Compare commits

..

1 Commits

Author SHA1 Message Date
Siddharth Ganesan
2f6ef2bf11 Speed up workflow export 2025-12-18 11:13:52 -08:00
511 changed files with 14395 additions and 89491 deletions

View File

@@ -48,19 +48,6 @@ jobs:
ENCRYPTION_KEY: '7cf672e460e430c1fba707575c2b0e2ad5a99dddf9b7b7e3b5646e630861db1c' # dummy key for CI only
run: bun run test
- name: Check schema and migrations are in sync
working-directory: packages/db
run: |
bunx drizzle-kit generate --config=./drizzle.config.ts
if [ -n "$(git status --porcelain ./migrations)" ]; then
echo "❌ Schema and migrations are out of sync!"
echo "Run 'cd packages/db && bunx drizzle-kit generate' and commit the new migrations."
git status --porcelain ./migrations
git diff ./migrations
exit 1
fi
echo "✅ Schema and migrations are in sync"
- name: Build application
env:
NODE_OPTIONS: '--no-warnings'

View File

@@ -188,7 +188,6 @@ DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
Then run the migrations:
```bash
cd packages/db # Required so drizzle picks correct .env file
bunx drizzle-kit migrate --config=./drizzle.config.ts
```

View File

@@ -258,7 +258,8 @@ export async function generateMetadata(props: {
const baseUrl = 'https://docs.sim.ai'
const fullUrl = `${baseUrl}${page.url}`
const ogImageUrl = `${baseUrl}/api/og?title=${encodeURIComponent(data.title)}`
const description = data.description || ''
const ogImageUrl = `${baseUrl}/api/og?title=${encodeURIComponent(data.title)}&category=DOCUMENTATION${description ? `&description=${encodeURIComponent(description)}` : ''}`
return {
title: data.title,

View File

@@ -1,23 +0,0 @@
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
export const metadata = {
title: 'Page Not Found',
}
export default function NotFound() {
return (
<DocsPage>
<DocsBody>
<div className='flex min-h-[60vh] flex-col items-center justify-center text-center'>
<h1 className='mb-4 bg-gradient-to-b from-[#8357FF] to-[#6F3DFA] bg-clip-text font-bold text-8xl text-transparent'>
404
</h1>
<h2 className='mb-2 font-semibold text-2xl text-foreground'>Page Not Found</h2>
<p className='text-muted-foreground'>
The page you're looking for doesn't exist or has been moved.
</p>
</div>
</DocsBody>
</DocsPage>
)
}

View File

@@ -39,10 +39,12 @@ async function loadGoogleFont(font: string, weights: string, text: string): Prom
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const title = searchParams.get('title') || 'Documentation'
const category = searchParams.get('category') || 'DOCUMENTATION'
const description = searchParams.get('description') || ''
const baseUrl = new URL(request.url).origin
const allText = `${title}docs.sim.ai`
const allText = `${title}${category}${description}docs.sim.ai`
const fontData = await loadGoogleFont('Geist', '400;500;600', allText)
return new ImageResponse(
@@ -57,7 +59,7 @@ export async function GET(request: NextRequest) {
fontFamily: 'Geist',
}}
>
{/* Base gradient layer - subtle purple tint across the entire image */}
{/* Base gradient layer - very subtle purple tint across the entire image */}
<div
style={{
position: 'absolute',
@@ -112,25 +114,56 @@ export async function GET(request: NextRequest) {
{/* Logo */}
<img src={`${baseUrl}/static/logo.png`} alt='sim' height={32} />
{/* Title */}
<span
{/* Category + Title + Description */}
<div
style={{
fontSize: getTitleFontSize(title),
fontWeight: 600,
color: '#ffffff',
lineHeight: 1.1,
letterSpacing: '-0.02em',
display: 'flex',
flexDirection: 'column',
gap: 12,
}}
>
{title}
</span>
<span
style={{
fontSize: 15,
fontWeight: 600,
color: '#802fff',
letterSpacing: '0.02em',
}}
>
{category}
</span>
<span
style={{
fontSize: getTitleFontSize(title),
fontWeight: 600,
color: '#ffffff',
lineHeight: 1.1,
letterSpacing: '-0.02em',
}}
>
{title}
</span>
{description && (
<span
style={{
fontSize: 18,
fontWeight: 400,
color: '#a1a1aa',
lineHeight: 1.4,
marginTop: 4,
}}
>
{description.length > 100 ? `${description.slice(0, 100)}...` : description}
</span>
)}
</div>
{/* Footer */}
<span
style={{
fontSize: 20,
fontSize: 15,
fontWeight: 500,
color: '#71717a',
color: '#52525b',
}}
>
docs.sim.ai

View File

@@ -58,7 +58,7 @@ export const metadata = {
'Comprehensive documentation for Sim - the visual workflow builder for AI applications. Create powerful AI agents, automation workflows, and data processing pipelines.',
images: [
{
url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation',
url: 'https://docs.sim.ai/api/og?title=Sim%20Documentation&category=DOCUMENTATION',
width: 1200,
height: 630,
alt: 'Sim Documentation',
@@ -72,7 +72,7 @@ export const metadata = {
'Comprehensive documentation for Sim - the visual workflow builder for AI applications.',
creator: '@simdotai',
site: '@simdotai',
images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation'],
images: ['https://docs.sim.ai/api/og?title=Sim%20Documentation&category=DOCUMENTATION'],
},
robots: {
index: true,

View File

@@ -6,10 +6,7 @@ import { source } from '@/lib/source'
export const revalidate = false
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ slug?: string[] }> }
) {
export async function GET(_req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> }) {
const { slug } = await params
let lang: (typeof i18n.languages)[number] = i18n.defaultLanguage

View File

@@ -120,117 +120,117 @@ import {
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
ahrefs: AhrefsIcon,
airtable: AirtableIcon,
apify: ApifyIcon,
apollo: ApolloIcon,
arxiv: ArxivIcon,
asana: AsanaIcon,
browser_use: BrowserUseIcon,
calendly: CalendlyIcon,
clay: ClayIcon,
confluence: ConfluenceIcon,
cursor: CursorIcon,
datadog: DatadogIcon,
discord: DiscordIcon,
dropbox: DropboxIcon,
duckduckgo: DuckDuckGoIcon,
dynamodb: DynamoDBIcon,
elasticsearch: ElasticsearchIcon,
elevenlabs: ElevenLabsIcon,
exa: ExaAIIcon,
file: DocumentIcon,
firecrawl: FirecrawlIcon,
github: GithubIcon,
gitlab: GitLabIcon,
gmail: GmailIcon,
google_calendar: GoogleCalendarIcon,
google_docs: GoogleDocsIcon,
google_drive: GoogleDriveIcon,
google_forms: GoogleFormsIcon,
google_groups: GoogleGroupsIcon,
google_search: GoogleIcon,
google_sheets: GoogleSheetsIcon,
google_slides: GoogleSlidesIcon,
google_vault: GoogleVaultIcon,
grafana: GrafanaIcon,
hubspot: HubspotIcon,
huggingface: HuggingFaceIcon,
hunter: HunterIOIcon,
image_generator: ImageIcon,
incidentio: IncidentioIcon,
intercom: IntercomIcon,
jina: JinaAIIcon,
jira: JiraIcon,
kalshi: KalshiIcon,
knowledge: PackageSearchIcon,
linear: LinearIcon,
linkedin: LinkedInIcon,
linkup: LinkupIcon,
mailchimp: MailchimpIcon,
mailgun: MailgunIcon,
mem0: Mem0Icon,
memory: BrainIcon,
microsoft_excel: MicrosoftExcelIcon,
microsoft_planner: MicrosoftPlannerIcon,
microsoft_teams: MicrosoftTeamsIcon,
mistral_parse: MistralIcon,
mongodb: MongoDBIcon,
mysql: MySQLIcon,
neo4j: Neo4jIcon,
notion: NotionIcon,
onedrive: MicrosoftOneDriveIcon,
openai: OpenAIIcon,
outlook: OutlookIcon,
parallel_ai: ParallelIcon,
perplexity: PerplexityIcon,
pinecone: PineconeIcon,
pipedrive: PipedriveIcon,
polymarket: PolymarketIcon,
postgresql: PostgresIcon,
posthog: PosthogIcon,
qdrant: QdrantIcon,
rds: RDSIcon,
reddit: RedditIcon,
resend: ResendIcon,
s3: S3Icon,
salesforce: SalesforceIcon,
search: SearchIcon,
sendgrid: SendgridIcon,
sentry: SentryIcon,
serper: SerperIcon,
servicenow: ServiceNowIcon,
sftp: SftpIcon,
sharepoint: MicrosoftSharepointIcon,
shopify: ShopifyIcon,
slack: SlackIcon,
smtp: SmtpIcon,
spotify: SpotifyIcon,
sqs: SQSIcon,
ssh: SshIcon,
stagehand: StagehandIcon,
stripe: StripeIcon,
stt: STTIcon,
supabase: SupabaseIcon,
tavily: TavilyIcon,
telegram: TelegramIcon,
thinking: BrainIcon,
translate: TranslateIcon,
trello: TrelloIcon,
tts: TTSIcon,
twilio_sms: TwilioIcon,
twilio_voice: TwilioIcon,
typeform: TypeformIcon,
video_generator: VideoIcon,
vision: EyeIcon,
wealthbox: WealthboxIcon,
webflow: WebflowIcon,
whatsapp: WhatsAppIcon,
wikipedia: WikipediaIcon,
wordpress: WordpressIcon,
x: xIcon,
youtube: YouTubeIcon,
zendesk: ZendeskIcon,
zep: ZepIcon,
zoom: ZoomIcon,
zep: ZepIcon,
zendesk: ZendeskIcon,
youtube: YouTubeIcon,
x: xIcon,
wordpress: WordpressIcon,
wikipedia: WikipediaIcon,
whatsapp: WhatsAppIcon,
webflow: WebflowIcon,
wealthbox: WealthboxIcon,
vision: EyeIcon,
video_generator: VideoIcon,
typeform: TypeformIcon,
twilio_voice: TwilioIcon,
twilio_sms: TwilioIcon,
tts: TTSIcon,
trello: TrelloIcon,
translate: TranslateIcon,
thinking: BrainIcon,
telegram: TelegramIcon,
tavily: TavilyIcon,
supabase: SupabaseIcon,
stt: STTIcon,
stripe: StripeIcon,
stagehand: StagehandIcon,
ssh: SshIcon,
sqs: SQSIcon,
spotify: SpotifyIcon,
smtp: SmtpIcon,
slack: SlackIcon,
shopify: ShopifyIcon,
sharepoint: MicrosoftSharepointIcon,
sftp: SftpIcon,
servicenow: ServiceNowIcon,
serper: SerperIcon,
sentry: SentryIcon,
sendgrid: SendgridIcon,
search: SearchIcon,
salesforce: SalesforceIcon,
s3: S3Icon,
resend: ResendIcon,
reddit: RedditIcon,
rds: RDSIcon,
qdrant: QdrantIcon,
posthog: PosthogIcon,
postgresql: PostgresIcon,
polymarket: PolymarketIcon,
pipedrive: PipedriveIcon,
pinecone: PineconeIcon,
perplexity: PerplexityIcon,
parallel_ai: ParallelIcon,
outlook: OutlookIcon,
openai: OpenAIIcon,
onedrive: MicrosoftOneDriveIcon,
notion: NotionIcon,
neo4j: Neo4jIcon,
mysql: MySQLIcon,
mongodb: MongoDBIcon,
mistral_parse: MistralIcon,
microsoft_teams: MicrosoftTeamsIcon,
microsoft_planner: MicrosoftPlannerIcon,
microsoft_excel: MicrosoftExcelIcon,
memory: BrainIcon,
mem0: Mem0Icon,
mailgun: MailgunIcon,
mailchimp: MailchimpIcon,
linkup: LinkupIcon,
linkedin: LinkedInIcon,
linear: LinearIcon,
knowledge: PackageSearchIcon,
kalshi: KalshiIcon,
jira: JiraIcon,
jina: JinaAIIcon,
intercom: IntercomIcon,
incidentio: IncidentioIcon,
image_generator: ImageIcon,
hunter: HunterIOIcon,
huggingface: HuggingFaceIcon,
hubspot: HubspotIcon,
grafana: GrafanaIcon,
google_vault: GoogleVaultIcon,
google_slides: GoogleSlidesIcon,
google_sheets: GoogleSheetsIcon,
google_groups: GoogleGroupsIcon,
google_forms: GoogleFormsIcon,
google_drive: GoogleDriveIcon,
google_docs: GoogleDocsIcon,
google_calendar: GoogleCalendarIcon,
google_search: GoogleIcon,
gmail: GmailIcon,
gitlab: GitLabIcon,
github: GithubIcon,
firecrawl: FirecrawlIcon,
file: DocumentIcon,
exa: ExaAIIcon,
elevenlabs: ElevenLabsIcon,
elasticsearch: ElasticsearchIcon,
dynamodb: DynamoDBIcon,
duckduckgo: DuckDuckGoIcon,
dropbox: DropboxIcon,
discord: DiscordIcon,
datadog: DatadogIcon,
cursor: CursorIcon,
confluence: ConfluenceIcon,
clay: ClayIcon,
calendly: CalendlyIcon,
browser_use: BrowserUseIcon,
asana: AsanaIcon,
arxiv: ArxivIcon,
apollo: ApolloIcon,
apify: ApifyIcon,
airtable: AirtableIcon,
ahrefs: AhrefsIcon,
}

View File

@@ -49,40 +49,40 @@ Die Modellaufschlüsselung zeigt:
<Tabs items={['Hosted Models', 'Bring Your Own API Key']}>
<Tab>
**Gehostete Modelle** - Sim stellt API-Schlüssel mit einem 2-fachen Preismultiplikator bereit:
**Gehostete Modelle** - Sim stellt API-Schlüssel mit einem 2,5-fachen Preismultiplikator bereit:
**OpenAI**
| Modell | Basispreis (Eingabe/Ausgabe) | Gehosteter Preis (Eingabe/Ausgabe) |
|-------|---------------------------|----------------------------|
| GPT-5.1 | 1,25 $ / 10,00 $ | 2,50 $ / 20,00 $ |
| GPT-5 | 1,25 $ / 10,00 $ | 2,50 $ / 20,00 $ |
| GPT-5 Mini | 0,25 $ / 2,00 $ | 0,50 $ / 4,00 $ |
| GPT-5 Nano | 0,05 $ / 0,40 $ | 0,10 $ / 0,80 $ |
| GPT-4o | 2,50 $ / 10,00 $ | 5,00 $ / 20,00 $ |
| GPT-4.1 | 2,00 $ / 8,00 $ | 4,00 $ / 16,00 $ |
| GPT-4.1 Mini | 0,40 $ / 1,60 $ | 0,80 $ / 3,20 $ |
| GPT-4.1 Nano | 0,10 $ / 0,40 $ | 0,20 $ / 0,80 $ |
| o1 | 15,00 $ / 60,00 $ | 30,00 $ / 120,00 $ |
| o3 | 2,00 $ / 8,00 $ | 4,00 $ / 16,00 $ |
| o4 Mini | 1,10 $ / 4,40 $ | 2,20 $ / 8,80 $ |
| GPT-5.1 | $1,25 / $10,00 | $3,13 / $25,00 |
| GPT-5 | $1,25 / $10,00 | $3,13 / $25,00 |
| GPT-5 Mini | $0,25 / $2,00 | $0,63 / $5,00 |
| GPT-5 Nano | $0,05 / $0,40 | $0,13 / $1,00 |
| GPT-4o | $2,50 / $10,00 | $6,25 / $25,00 |
| GPT-4.1 | $2,00 / $8,00 | $5,00 / $20,00 |
| GPT-4.1 Mini | $0,40 / $1,60 | $1,00 / $4,00 |
| GPT-4.1 Nano | $0,10 / $0,40 | $0,25 / $1,00 |
| o1 | $15,00 / $60,00 | $37,50 / $150,00 |
| o3 | $2,00 / $8,00 | $5,00 / $20,00 |
| o4 Mini | $1,10 / $4,40 | $2,75 / $11,00 |
**Anthropic**
| Modell | Basispreis (Eingabe/Ausgabe) | Gehosteter Preis (Eingabe/Ausgabe) |
|-------|---------------------------|----------------------------|
| Claude Opus 4.5 | 5,00 $ / 25,00 $ | 10,00 $ / 50,00 $ |
| Claude Opus 4.1 | 15,00 $ / 75,00 $ | 30,00 $ / 150,00 $ |
| Claude Sonnet 4.5 | 3,00 $ / 15,00 $ | 6,00 $ / 30,00 $ |
| Claude Sonnet 4.0 | 3,00 $ / 15,00 $ | 6,00 $ / 30,00 $ |
| Claude Haiku 4.5 | 1,00 $ / 5,00 $ | 2,00 $ / 10,00 $ |
| Claude Opus 4.5 | $5,00 / $25,00 | $12,50 / $62,50 |
| Claude Opus 4.1 | $15,00 / $75,00 | $37,50 / $187,50 |
| Claude Sonnet 4.5 | $3,00 / $15,00 | $7,50 / $37,50 |
| Claude Sonnet 4.0 | $3,00 / $15,00 | $7,50 / $37,50 |
| Claude Haiku 4.5 | $1,00 / $5,00 | $2,50 / $12,50 |
**Google**
| Modell | Basispreis (Eingabe/Ausgabe) | Gehosteter Preis (Eingabe/Ausgabe) |
|-------|---------------------------|----------------------------|
| Gemini 3 Pro Preview | 2,00 $ / 12,00 $ | 4,00 $ / 24,00 $ |
| Gemini 2.5 Pro | 1,25 $ / 10,00 $ | 2,50 $ / 20,00 $ |
| Gemini 2.5 Flash | 0,30 $ / 2,50 $ | 0,60 $ / 5,00 $ |
| Gemini 3 Pro Preview | $2,00 / $12,00 | $5,00 / $30,00 |
| Gemini 2.5 Pro | $0,15 / $0,60 | $0,38 / $1,50 |
| Gemini 2.5 Flash | $0,15 / $0,60 | $0,38 / $1,50 |
*Der 2x-Multiplikator deckt Infrastruktur- und API-Verwaltungskosten ab.*
*Der 2,5-fache Multiplikator deckt Infrastruktur- und API-Verwaltungskosten ab.*
</Tab>
<Tab>
@@ -185,11 +185,11 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
Verschiedene Abonnementpläne haben unterschiedliche Nutzungslimits:
| Plan | Monatliches Nutzungslimit | Ratenlimits (pro Minute) |
| Plan | Monatliches Nutzungslimit | Rate-Limits (pro Minute) |
|------|-------------------|-------------------------|
| **Free** | 20 $ | 5 synchron, 10 asynchron |
| **Pro** | 100 $ | 10 synchron, 50 asynchron |
| **Team** | 500 $ (gepoolt) | 50 synchron, 100 asynchron |
| **Free** | $10 | 5 sync, 10 async |
| **Pro** | $100 | 10 sync, 50 async |
| **Team** | $500 (gepoolt) | 50 sync, 100 async |
| **Enterprise** | Individuell | Individuell |
## Abrechnungsmodell

View File

@@ -35,87 +35,81 @@ Sobald Ihre Dokumente verarbeitet sind, können Sie die einzelnen Chunks anzeige
<Image src="/static/knowledgebase/knowledgebase.png" alt="Dokumentchunk-Ansicht mit verarbeiteten Inhalten" width={800} height={500} />
### Chunk-Konfiguration
- **Standardgröße der Chunks**: 1.024 Zeichen
- **Konfigurierbarer Bereich**: 100-4.000 Zeichen pro Chunk
- **Intelligente Überlappung**: Standardmäßig 200 Zeichen zur Kontexterhaltung
- **Hierarchische Aufteilung**: Respektiert Dokumentstruktur (Abschnitte, Absätze, Sätze)
Beim Erstellen einer Wissensdatenbank können Sie konfigurieren, wie Dokumente in Chunks aufgeteilt werden:
| Einstellung | Einheit | Standard | Bereich | Beschreibung |
|---------|------|---------|-------|-------------|
| **Maximale Chunk-Größe** | Tokens | 1.024 | 100-4.000 | Maximale Größe jedes Chunks (1 Token ≈ 4 Zeichen) |
| **Minimale Chunk-Größe** | Zeichen | 1 | 1-2.000 | Minimale Chunk-Größe, um winzige Fragmente zu vermeiden |
| **Überlappung** | Zeichen | 200 | 0-500 | Kontextüberlappung zwischen aufeinanderfolgenden Chunks |
- **Hierarchische Aufteilung**: Berücksichtigt die Dokumentstruktur (Abschnitte, Absätze, Sätze)
### Bearbeitungsmöglichkeiten
### Bearbeitungsfunktionen
- **Chunk-Inhalt bearbeiten**: Textinhalt einzelner Chunks ändern
- **Chunk-Grenzen anpassen**: Chunks nach Bedarf zusammenführen oder aufteilen
- **Chunk-Grenzen anpassen**: Chunks bei Bedarf zusammenführen oder teilen
- **Metadaten hinzufügen**: Chunks mit zusätzlichem Kontext anreichern
- **Massenoperationen**: Mehrere Chunks effizient verwalten
- **Massenoperationen**: Effiziente Verwaltung mehrerer Chunks
## Erweiterte PDF-Verarbeitung
Für PDF-Dokumente bietet Sim erweiterte Verarbeitungsfunktionen:
### OCR-Unterstützung
Wenn mit Azure oder [Mistral OCR](https://docs.mistral.ai/ocr/) konfiguriert:
Bei Konfiguration mit Azure oder [Mistral OCR](https://docs.mistral.ai/ocr/):
- **Verarbeitung gescannter Dokumente**: Text aus bildbasierten PDFs extrahieren
- **Verarbeitung gemischter Inhalte**: PDFs mit Text und Bildern verarbeiten
- **Umgang mit gemischten Inhalten**: Verarbeitung von PDFs mit Text und Bildern
- **Hohe Genauigkeit**: Fortschrittliche KI-Modelle gewährleisten präzise Textextraktion
## Verwendung des Knowledge-Blocks in Workflows
## Verwendung des Wissensblocks in Workflows
Sobald Ihre Dokumente verarbeitet sind, können Sie sie in Ihren KI-Workflows über den Knowledge-Block verwenden. Dies ermöglicht Retrieval-Augmented Generation (RAG), wodurch Ihre KI-Agenten auf Ihre Dokumentinhalte zugreifen und darüber nachdenken können, um genauere, kontextbezogene Antworten zu liefern.
Sobald Ihre Dokumente verarbeitet sind, können Sie sie in Ihren KI-Workflows über den Wissensblock nutzen. Dies ermöglicht Retrieval-Augmented Generation (RAG), wodurch Ihre KI-Agenten auf Ihre Dokumentinhalte zugreifen und darüber nachdenken können, um genauere, kontextbezogene Antworten zu liefern.
<Image src="/static/knowledgebase/knowledgebase-2.png" alt="Verwendung des Knowledge-Blocks in Workflows" width={800} height={500} />
<Image src="/static/knowledgebase/knowledgebase-2.png" alt="Verwendung des Wissensblocks in Workflows" width={800} height={500} />
### Knowledge-Block-Funktionen
- **Semantische Suche**: Relevante Inhalte mithilfe natürlichsprachlicher Abfragen finden
- **Kontextintegration**: Relevante Chunks automatisch in Agenten-Prompts einbinden
- **Dynamisches Abrufen**: Suche erfolgt in Echtzeit während der Workflow-Ausführung
- **Relevanz-Bewertung**: Ergebnisse nach semantischer Ähnlichkeit sortiert
### Funktionen des Wissensblocks
- **Semantische Suche**: Relevante Inhalte mit natürlichsprachlichen Abfragen finden
- **Kontextintegration**: Automatisches Einbinden relevanter Chunks in Agenten-Prompts
- **Dynamischer Abruf**: Suche erfolgt in Echtzeit während der Workflow-Ausführung
- **Relevanzbewertung**: Ergebnisse nach semantischer Ähnlichkeit geordnet
### Integrationsoptionen
- **System-Prompts**: Stellen Sie Ihren KI-Agenten Kontext bereit
- **Dynamischer Kontext**: Suchen und fügen Sie relevante Informationen während Konversationen hinzu
- **Multi-Dokument-Suche**: Durchsuchen Sie Ihre gesamte Wissensdatenbank
- **Gefilterte Suche**: Kombinieren Sie mit Tags für präzises Abrufen von Inhalten
- **System-Prompts**: Kontext für Ihre KI-Agenten bereitstellen
- **Dynamischer Kontext**: Suche und Einbindung relevanter Informationen während Gesprächen
- **Dokumentübergreifende Suche**: Abfrage über Ihre gesamte Wissensdatenbank
- **Gefilterte Suche**: Kombination mit Tags für präzisen Inhaltsabruf
## Vektor-Suchtechnologie
## Vektorsuchtechnologie
Sim verwendet Vektorsuche, die von [pgvector](https://github.com/pgvector/pgvector) unterstützt wird, um die Bedeutung und den Kontext Ihrer Inhalte zu verstehen:
### Semantisches Verständnis
- **Kontextuelle Suche**: Findet relevante Inhalte, auch wenn exakte Schlüsselwörter nicht übereinstimmen
- **Konzeptbasiertes Abrufen**: Versteht Beziehungen zwischen Ideen
- **Konzeptbasierte Abfrage**: Versteht Beziehungen zwischen Ideen
- **Mehrsprachige Unterstützung**: Funktioniert über verschiedene Sprachen hinweg
- **Synonymerkennung**: Findet verwandte Begriffe und Konzepte
### Suchfunktionen
- **Natürlichsprachige Abfragen**: Stellen Sie Fragen in einfachem Deutsch
- **Natürlichsprachige Abfragen**: Stellen Sie Fragen in natürlicher Sprache
- **Ähnlichkeitssuche**: Finden Sie konzeptionell ähnliche Inhalte
- **Hybride Suche**: Kombiniert Vektor- und traditionelle Schlüsselwortsuche
- **Konfigurierbare Ergebnisse**: Steuern Sie die Anzahl und Relevanzschwelle der Ergebnisse
- **Hybridsuche**: Kombiniert Vektor- und traditionelle Schlüsselwortsuche
- **Konfigurierbare Ergebnisse**: Steuern Sie die Anzahl und den Relevanz-Schwellenwert der Ergebnisse
## Dokumentenverwaltung
### Organisationsfunktionen
- **Massen-Upload**: Laden Sie mehrere Dateien gleichzeitig über die asynchrone API hoch
- **Verarbeitungsstatus**: Echtzeit-Updates zur Dokumentenverarbeitung
- **Suchen und filtern**: Finden Sie Dokumente schnell in großen Sammlungen
- **Massenupload**: Laden Sie mehrere Dateien gleichzeitig über die asynchrone API hoch
- **Verarbeitungsstatus**: Echtzeit-Updates zum Dokumentenverarbeitungsprozess
- **Suchen und Filtern**: Finden Sie Dokumente schnell in großen Sammlungen
- **Metadaten-Tracking**: Automatische Erfassung von Dateiinformationen und Verarbeitungsdetails
### Sicherheit und Datenschutz
- **Sichere Speicherung**: Dokumente werden mit Sicherheit auf Unternehmensniveau gespeichert
- **Zugriffskontrolle**: Workspace-basierte Berechtigungen
- **Verarbeitungsisolierung**: Jeder Workspace hat isolierte Dokumentenverarbeitung
- **Verarbeitungsisolierung**: Jeder Workspace hat eine isolierte Dokumentenverarbeitung
- **Datenaufbewahrung**: Konfigurieren Sie Richtlinien zur Dokumentenaufbewahrung
## Erste Schritte
1. **Navigieren Sie zu Ihrer Wissensdatenbank**: Zugriff über Ihre Workspace-Seitenleiste
2. **Dokumente hochladen**: Ziehen und ablegen oder Dateien zum Hochladen auswählen
3. **Verarbeitung überwachen**: Beobachten Sie, wie Dokumente verarbeitet und in Abschnitte unterteilt werden
4. **Abschnitte erkunden**: Zeigen Sie die verarbeiteten Inhalte an und bearbeiten Sie sie
5. **Zu Workflows hinzufügen**: Verwenden Sie den Knowledge-Block, um mit Ihren KI-Agenten zu integrieren
2. **Dokumente hochladen**: Drag & Drop oder wählen Sie Dateien zum Hochladen aus
3. **Verarbeitung überwachen**: Beobachten Sie, wie Dokumente verarbeitet und in Chunks aufgeteilt werden
4. **Chunks erkunden**: Sehen und bearbeiten Sie die verarbeiteten Inhalte
5. **Zu Workflows hinzufügen**: Verwenden Sie den Wissensblock, um ihn in Ihre KI-Agenten zu integrieren
Die Wissensdatenbank verwandelt Ihre statischen Dokumente in eine intelligente, durchsuchbare Ressource, die Ihre KI-Workflows für fundiertere und kontextbezogene Antworten nutzen können.
Die Wissensdatenbank verwandelt Ihre statischen Dokumente in eine intelligente, durchsuchbare Ressource, die Ihre KI-Workflows für fundiertere und kontextbezogenere Antworten nutzen können.

View File

@@ -38,7 +38,6 @@ Erstellen Sie einen neuen Kontakt in Intercom mit E-Mail, external_id oder Rolle
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `role` | string | Nein | Die Rolle des Kontakts. Akzeptiert 'user' oder 'lead'. Standardmäßig 'lead', wenn nicht angegeben. |
| `email` | string | Nein | Die E-Mail-Adresse des Kontakts |
| `external_id` | string | Nein | Eine eindeutige Kennung für den Kontakt, die vom Client bereitgestellt wird |
| `phone` | string | Nein | Die Telefonnummer des Kontakts |
@@ -46,10 +45,9 @@ Erstellen Sie einen neuen Kontakt in Intercom mit E-Mail, external_id oder Rolle
| `avatar` | string | Nein | Eine Avatar-Bild-URL für den Kontakt |
| `signed_up_at` | number | Nein | Der Zeitpunkt der Registrierung des Benutzers als Unix-Zeitstempel |
| `last_seen_at` | number | Nein | Der Zeitpunkt, zu dem der Benutzer zuletzt gesehen wurde, als Unix-Zeitstempel |
| `owner_id` | string | Nein | Die ID eines Administrators, dem die Kontoverantwortung für den Kontakt zugewiesen wurde |
| `unsubscribed_from_emails` | boolean | Nein | Ob der Kontakt von E-Mails abgemeldet ist |
| `custom_attributes` | string | Nein | Benutzerdefinierte Attribute als JSON-Objekt \(z. B. \{"attribute_name": "value"\}\) |
| `company_id` | string | Nein | Unternehmens-ID, mit der der Kontakt bei der Erstellung verknüpft werden soll |
| `owner_id` | string | Nein | Die ID eines Administrators, dem die Kontoinhaberschaft des Kontakts zugewiesen wurde |
| `unsubscribed_from_emails` | boolean | Nein | Ob der Kontakt E-Mails abbestellt hat |
| `custom_attributes` | string | Nein | Benutzerdefinierte Attribute als JSON-Objekt \(z.B. \{"attribute_name": "value"\}\) |
#### Ausgabe
@@ -83,19 +81,16 @@ Einen bestehenden Kontakt in Intercom aktualisieren
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | Ja | Kontakt-ID, die aktualisiert werden soll |
| `role` | string | Nein | Die Rolle des Kontakts. Akzeptiert 'user' oder 'lead'. |
| `external_id` | string | Nein | Eine eindeutige Kennung für den Kontakt, die vom Client bereitgestellt wird |
| `contactId` | string | Ja | Zu aktualisierende Kontakt-ID |
| `email` | string | Nein | Die E-Mail-Adresse des Kontakts |
| `phone` | string | Nein | Die Telefonnummer des Kontakts |
| `name` | string | Nein | Der Name des Kontakts |
| `avatar` | string | Nein | Eine Avatar-Bild-URL für den Kontakt |
| `signed_up_at` | number | Nein | Der Zeitpunkt der Registrierung des Benutzers als Unix-Zeitstempel |
| `last_seen_at` | number | Nein | Der Zeitpunkt, zu dem der Benutzer zuletzt gesehen wurde, als Unix-Zeitstempel |
| `owner_id` | string | Nein | Die ID eines Administrators, dem die Kontoverantwortung für den Kontakt zugewiesen wurde |
| `unsubscribed_from_emails` | boolean | Nein | Ob der Kontakt von E-Mails abgemeldet ist |
| `custom_attributes` | string | Nein | Benutzerdefinierte Attribute als JSON-Objekt \(z. B. \{"attribute_name": "value"\}\) |
| `company_id` | string | Nein | Unternehmens-ID, mit der der Kontakt verknüpft werden soll |
| `signed_up_at` | number | Nein | Der Zeitpunkt der Registrierung des Benutzers als Unix-Timestamp |
| `last_seen_at` | number | Nein | Der Zeitpunkt, zu dem der Benutzer zuletzt gesehen wurde, als Unix-Timestamp |
| `owner_id` | string | Nein | Die ID eines Administrators, dem die Kontoeigentümerschaft des Kontakts zugewiesen wurde |
| `unsubscribed_from_emails` | boolean | Nein | Ob der Kontakt E-Mails abbestellt hat |
| `custom_attributes` | string | Nein | Benutzerdefinierte Attribute als JSON-Objekt \(z.B. \{"attribut_name": "wert"\}\) |
#### Output
@@ -130,11 +125,9 @@ Suche nach Kontakten in Intercom mit einer Abfrage
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `query` | string | Ja | Suchabfrage (z. B. \{"field":"email","operator":"=","value":"user@example.com"\}) |
| `query` | string | Ja | Suchabfrage (z.B., \{"field":"email","operator":"=","value":"user@example.com"\}) |
| `per_page` | number | Nein | Anzahl der Ergebnisse pro Seite (max: 150) |
| `starting_after` | string | Nein | Cursor für Paginierung |
| `sort_field` | string | Nein | Feld zum Sortieren (z. B. "name", "created_at", "last_seen_at") |
| `sort_order` | string | Nein | Sortierreihenfolge: "ascending" oder "descending" |
#### Ausgabe
@@ -170,13 +163,12 @@ Ein Unternehmen in Intercom erstellen oder aktualisieren
| --------- | ---- | -------- | ----------- |
| `company_id` | string | Ja | Ihre eindeutige Kennung für das Unternehmen |
| `name` | string | Nein | Der Name des Unternehmens |
| `website` | string | Nein | Die Website des Unternehmens |
| `plan` | string | Nein | Der Name des Unternehmensplans |
| `website` | string | Nein | Die Unternehmenswebsite |
| `plan` | string | Nein | Der Unternehmensplan |
| `size` | number | Nein | Die Anzahl der Mitarbeiter im Unternehmen |
| `industry` | string | Nein | Die Branche, in der das Unternehmen tätig ist |
| `monthly_spend` | number | Nein | Wie viel Umsatz das Unternehmen für Ihr Geschäft generiert. Hinweis: Dieses Feld rundet Dezimalzahlen auf ganze Zahlen ab (z. B. wird 155,98 zu 155) |
| `monthly_spend` | number | Nein | Wie viel Umsatz das Unternehmen für Ihr Geschäft generiert. Hinweis: Dieses Feld kürzt Dezimalzahlen auf ganze Zahlen \(z.B. wird aus 155,98 die Zahl 155\) |
| `custom_attributes` | string | Nein | Benutzerdefinierte Attribute als JSON-Objekt |
| `remote_created_at` | number | Nein | Der Zeitpunkt, zu dem das Unternehmen von Ihnen erstellt wurde, als Unix-Zeitstempel |
#### Ausgabe
@@ -212,7 +204,6 @@ Listet alle Unternehmen von Intercom mit Paginierungsunterstützung auf. Hinweis
| --------- | ---- | -------- | ----------- |
| `per_page` | number | Nein | Anzahl der Ergebnisse pro Seite |
| `page` | number | Nein | Seitennummer |
| `starting_after` | string | Nein | Cursor für Paginierung (bevorzugt gegenüber seitenbasierter Paginierung) |
#### Ausgabe
@@ -230,8 +221,7 @@ Eine einzelne Konversation anhand der ID von Intercom abrufen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Ja | Konversations-ID zum Abrufen |
| `display_as` | string | Nein | Auf "plaintext" setzen, um Nachrichten als reinen Text abzurufen |
| `include_translations` | boolean | Nein | Wenn true, werden Konversationsteile in die erkannte Sprache der Konversation übersetzt |
| `display_as` | string | Nein | Auf "plaintext" setzen, um Nachrichten im Klartext abzurufen |
#### Ausgabe
@@ -248,10 +238,8 @@ Alle Konversationen von Intercom mit Paginierungsunterstützung auflisten
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `per_page` | number | Nein | Anzahl der Ergebnisse pro Seite (max: 150) |
| `per_page` | number | Nein | Anzahl der Ergebnisse pro Seite \(max: 150\) |
| `starting_after` | string | Nein | Cursor für Paginierung |
| `sort` | string | Nein | Feld zum Sortieren (z. B. "waiting_since", "updated_at", "created_at") |
| `order` | string | Nein | Sortierreihenfolge: "asc" (aufsteigend) oder "desc" (absteigend) |
#### Ausgabe
@@ -268,12 +256,11 @@ Als Administrator auf eine Konversation in Intercom antworten
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Ja | Konversations-ID zum Antworten |
| `conversationId` | string | Ja | Konversations-ID, auf die geantwortet werden soll |
| `message_type` | string | Ja | Nachrichtentyp: "comment" oder "note" |
| `body` | string | Ja | Der Textinhalt der Antwort |
| `admin_id` | string | Nein | Die ID des Administrators, der die Antwort verfasst. Falls nicht angegeben, wird ein Standard-Administrator (Operator/Fin) verwendet. |
| `attachment_urls` | string | Nein | Kommagetrennte Liste von Bild-URLs (max. 10) |
| `created_at` | number | Nein | Unix-Zeitstempel für den Zeitpunkt der Erstellung der Antwort. Falls nicht angegeben, wird die aktuelle Zeit verwendet. |
| `admin_id` | string | Nein | Die ID des Administrators, der die Antwort verfasst. Wenn nicht angegeben, wird ein Standard-Administrator \(Operator/Fin\) verwendet. |
| `attachment_urls` | string | Nein | Kommagetrennte Liste von Bild-URLs \(max. 10\) |
#### Ausgabe
@@ -291,10 +278,8 @@ Nach Konversationen in Intercom mit einer Abfrage suchen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `query` | string | Ja | Suchabfrage als JSON-Objekt |
| `per_page` | number | Nein | Anzahl der Ergebnisse pro Seite \(max: 150\) |
| `per_page` | number | Nein | Anzahl der Ergebnisse pro Seite (max: 150) |
| `starting_after` | string | Nein | Cursor für Paginierung |
| `sort_field` | string | Nein | Feld, nach dem sortiert werden soll \(z. B. "created_at", "updated_at"\) |
| `sort_order` | string | Nein | Sortierreihenfolge: "ascending" oder "descending" |
#### Ausgabe
@@ -312,12 +297,8 @@ Ein neues Ticket in Intercom erstellen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `ticket_type_id` | string | Ja | Die ID des Ticket-Typs |
| `contacts` | string | Ja | JSON-Array von Kontaktkennungen \(z. B. \[\{"id": "contact_id"\}\]\) |
| `contacts` | string | Ja | JSON-Array von Kontakt-Identifikatoren (z.B. \{"id": "contact_id"\}) |
| `ticket_attributes` | string | Ja | JSON-Objekt mit Ticket-Attributen einschließlich _default_title_ und _default_description_ |
| `company_id` | string | Nein | Unternehmens-ID, mit der das Ticket verknüpft werden soll |
| `created_at` | number | Nein | Unix-Zeitstempel für den Zeitpunkt der Ticket-Erstellung. Wenn nicht angegeben, wird die aktuelle Zeit verwendet. |
| `conversation_to_link_id` | string | Nein | ID einer vorhandenen Konversation, die mit diesem Ticket verknüpft werden soll |
| `disable_notifications` | boolean | Nein | Wenn true, werden Benachrichtigungen bei der Ticket-Erstellung unterdrückt |
#### Output
@@ -349,17 +330,15 @@ Eine neue vom Administrator initiierte Nachricht in Intercom erstellen und sende
#### Input
| Parameter | Typ | Erforderlich | Beschreibung |
| Parameter | Type | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `message_type` | string | Ja | Nachrichtentyp: "inapp" für In-App-Nachrichten oder "email" für E-Mail-Nachrichten |
| `template` | string | Ja | Nachrichtenvorlagenstil: "plain" für einfachen Text oder "personal" für personalisierten Stil |
| `message_type` | string | Ja | Nachrichtentyp: "inapp" oder "email" |
| `subject` | string | Nein | Der Betreff der Nachricht \(für E-Mail-Typ\) |
| `body` | string | Ja | Der Inhalt der Nachricht |
| `from_type` | string | Ja | Absendertyp: "admin" |
| `from_id` | string | Ja | Die ID des Administrators, der die Nachricht sendet |
| `to_type` | string | Ja | Empfängertyp: "contact" |
| `to_id` | string | Ja | Die ID des Kontakts, der die Nachricht empfängt |
| `created_at` | number | Nein | Unix-Zeitstempel für den Zeitpunkt der Nachrichtenerstellung. Wenn nicht angegeben, wird die aktuelle Zeit verwendet. |
#### Output

View File

@@ -90,20 +90,14 @@ Ein Jira-Issue erstellen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Ja | Ihre Jira-Domain \(z.B. ihrfirma.atlassian.net\) |
| `domain` | string | Ja | Ihre Jira-Domain (z.B. ihrfirma.atlassian.net) |
| `projectId` | string | Ja | Projekt-ID für das Issue |
| `summary` | string | Ja | Zusammenfassung für das Issue |
| `description` | string | Nein | Beschreibung für das Issue |
| `priority` | string | Nein | Prioritäts-ID oder -Name für das Issue \(z.B. "10000" oder "High"\) |
| `assignee` | string | Nein | Account-ID des Bearbeiters für das Issue |
| `cloudId` | string | Nein | Jira Cloud-ID für die Instanz. Wenn nicht angegeben, wird sie über die Domain abgerufen. |
| `issueType` | string | Ja | Typ des zu erstellenden Issues \(z.B. Task, Story\) |
| `labels` | array | Nein | Labels für das Issue \(Array von Label-Namen\) |
| `duedate` | string | Nein | Fälligkeitsdatum für das Issue \(Format: YYYY-MM-DD\) |
| `reporter` | string | Nein | Account-ID des Melders für das Issue |
| `environment` | string | Nein | Umgebungsinformationen für das Issue |
| `customFieldId` | string | Nein | Benutzerdefinierte Feld-ID \(z.B. customfield_10001\) |
| `customFieldValue` | string | Nein | Wert für das benutzerdefinierte Feld |
| `priority` | string | Nein | Priorität für das Issue |
| `assignee` | string | Nein | Bearbeiter für das Issue |
| `cloudId` | string | Nein | Jira Cloud-ID für die Instanz. Wenn nicht angegeben, wird sie anhand der Domain abgerufen. |
| `issueType` | string | Ja | Art des zu erstellenden Issues (z.B. Task, Story) |
#### Ausgabe
@@ -113,7 +107,6 @@ Ein Jira-Issue erstellen
| `issueKey` | string | Erstellter Issue-Key \(z.B. PROJ-123\) |
| `summary` | string | Issue-Zusammenfassung |
| `url` | string | URL zum erstellten Issue |
| `assigneeId` | string | Account-ID des zugewiesenen Benutzers \(falls zugewiesen\) |
### `jira_bulk_read`
@@ -527,30 +520,6 @@ Einen Beobachter von einem Jira-Issue entfernen
| `issueKey` | string | Issue-Key |
| `watcherAccountId` | string | Account-ID des entfernten Beobachters |
### `jira_get_users`
Jira-Benutzer abrufen. Wenn eine Account-ID angegeben wird, wird ein einzelner Benutzer zurückgegeben. Andernfalls wird eine Liste aller Benutzer zurückgegeben.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Ja | Ihre Jira-Domain \(z.B. ihrfirma.atlassian.net\) |
| `accountId` | string | Nein | Optionale Account-ID, um einen bestimmten Benutzer abzurufen. Wenn nicht angegeben, werden alle Benutzer zurückgegeben. |
| `startAt` | number | Nein | Der Index des ersten zurückzugebenden Benutzers \(für Paginierung, Standard: 0\) |
| `maxResults` | number | Nein | Maximale Anzahl der zurückzugebenden Benutzer \(Standard: 50\) |
| `cloudId` | string | Nein | Jira Cloud-ID für die Instanz. Wenn nicht angegeben, wird sie anhand der Domain abgerufen. |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `ts` | string | Zeitstempel der Operation |
| `users` | json | Array von Benutzern mit accountId, displayName, emailAddress, active-Status und avatarUrls |
| `total` | number | Gesamtanzahl der zurückgegebenen Benutzer |
| `startAt` | number | Startindex für Paginierung |
| `maxResults` | number | Maximale Ergebnisse pro Seite |
## Hinweise
- Kategorie: `tools`

View File

@@ -10,52 +10,55 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
color="#F64F9E"
/>
## Nutzungsanweisungen
## Gebrauchsanweisung
Integrieren Sie Memory in den Workflow. Kann Erinnerungen hinzufügen, abrufen, alle Erinnerungen abrufen und Erinnerungen löschen.
Memory in den Workflow integrieren. Kann Erinnerungen hinzufügen, eine Erinnerung abrufen, alle Erinnerungen abrufen und Erinnerungen löschen.
## Tools
### `memory_add`
Fügen Sie eine neue Erinnerung zur Datenbank hinzu oder hängen Sie sie an eine bestehende Erinnerung mit derselben ID an.
Füge eine neue Erinnerung zur Datenbank hinzu oder ergänze bestehende Erinnerungen mit derselben ID.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Nein | Konversationskennung \(z. B. user-123, session-abc\). Wenn bereits eine Erinnerung mit dieser conversationId existiert, wird die neue Nachricht an diese angehängt. |
| `id` | string | Nein | Legacy-Parameter für Konversationskennung. Verwenden Sie stattdessen conversationId. Wird aus Gründen der Abwärtskompatibilität bereitgestellt. |
| `role` | string | Ja | Rolle für Agent-Erinnerung \(user, assistant oder system\) |
| `conversationId` | string | Nein | Konversationskennung (z.B. user-123, session-abc). Wenn bereits eine Erinnerung mit dieser conversationId für diesen Block existiert, wird die neue Nachricht angehängt. |
| `id` | string | Nein | Legacy-Parameter für die Konversationskennung. Verwenden Sie stattdessen conversationId. r Abwärtskompatibilität bereitgestellt. |
| `role` | string | Ja | Rolle für Agent-Erinnerung (user, assistant oder system) |
| `content` | string | Ja | Inhalt für Agent-Erinnerung |
| `blockId` | string | Nein | Optionale Block-ID. Wenn nicht angegeben, wird die aktuelle Block-ID aus dem Ausführungskontext verwendet oder standardmäßig "default" gesetzt. |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Ob die Erinnerung erfolgreich hinzugefügt wurde |
| `memories` | array | Array von Erinnerungsobjekten einschließlich der neuen oder aktualisierten Erinnerung |
| `success` | boolean | Ob der Speicher erfolgreich hinzugefügt wurde |
| `memories` | array | Array von Speicherobjekten einschließlich des neuen oder aktualisierten Speichers |
| `error` | string | Fehlermeldung, falls der Vorgang fehlgeschlagen ist |
### `memory_get`
Erinnerung nach conversationId abrufen. Gibt übereinstimmende Erinnerungen zurück.
Erinnerungen nach conversationId, blockId, blockName oder einer Kombination abrufen. Gibt alle übereinstimmenden Erinnerungen zurück.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Nein | Konversationskennung \(z. B. user-123, session-abc\). Gibt Erinnerungen für diese Konversation zurück. |
| `id` | string | Nein | Legacy-Parameter für Konversationskennung. Verwenden Sie stattdessen conversationId. Wird aus Gründen der Abwärtskompatibilität bereitgestellt. |
| `conversationId` | string | Nein | Konversationskennung (z.B. user-123, session-abc). Wenn allein angegeben, werden alle Erinnerungen für diese Konversation über alle Blöcke hinweg zurückgegeben. |
| `id` | string | Nein | Legacy-Parameter für die Konversationskennung. Verwenden Sie stattdessen conversationId. r Abwärtskompatibilität bereitgestellt. |
| `blockId` | string | Nein | Block-Kennung. Wenn allein angegeben, werden alle Erinnerungen für diesen Block über alle Konversationen hinweg zurückgegeben. Wenn mit conversationId angegeben, werden Erinnerungen für diese spezifische Konversation in diesem Block zurückgegeben. |
| `blockName` | string | Nein | Blockname. Alternative zu blockId. Wenn allein angegeben, werden alle Erinnerungen für Blöcke mit diesem Namen zurückgegeben. Wenn mit conversationId angegeben, werden Erinnerungen für diese Konversation in Blöcken mit diesem Namen zurückgegeben. |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Ob der Speicher erfolgreich abgerufen wurde |
| `memories` | array | Array von Speicherobjekten mit conversationId- und data-Feldern |
| `success` | boolean | Ob die Erinnerung erfolgreich abgerufen wurde |
| `memories` | array | Array von Speicherobjekten mit conversationId, blockId, blockName und data-Feldern |
| `message` | string | Erfolgs- oder Fehlermeldung |
| `error` | string | Fehlermeldung, falls fehlgeschlagen |
| `error` | string | Fehlermeldung, wenn der Vorgang fehlgeschlagen ist |
### `memory_get_all`
@@ -70,29 +73,31 @@ Alle Speicher aus der Datenbank abrufen
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Ob alle Speicher erfolgreich abgerufen wurden |
| `memories` | array | Array aller Speicherobjekte mit key-, conversationId- und data-Feldern |
| `success` | boolean | Ob alle Erinnerungen erfolgreich abgerufen wurden |
| `memories` | array | Array aller Speicherobjekte mit key, conversationId, blockId, blockName und data-Feldern |
| `message` | string | Erfolgs- oder Fehlermeldung |
| `error` | string | Fehlermeldung, falls fehlgeschlagen |
| `error` | string | Fehlermeldung, wenn der Vorgang fehlgeschlagen ist |
### `memory_delete`
Speicher nach conversationId löschen.
Löschen von Erinnerungen nach conversationId, blockId, blockName oder einer Kombination davon. Unterstützt Massenlöschung.
#### Eingabe
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Nein | Konversationskennung (z. B. user-123, session-abc). Löscht alle Speicher für diese Konversation. |
| `id` | string | Nein | Legacy-Parameter für Konversationskennung. Verwenden Sie stattdessen conversationId. Wird aus Gründen der Abwärtskompatibilität bereitgestellt. |
| `conversationId` | string | Nein | Konversationskennung (z.B. user-123, session-abc). Wenn allein angegeben, werden alle Erinnerungen für diese Konversation über alle Blöcke hinweg gelöscht. |
| `id` | string | Nein | Legacy-Parameter für die Konversationskennung. Verwenden Sie stattdessen conversationId. r Abwärtskompatibilität bereitgestellt. |
| `blockId` | string | Nein | Block-Kennung. Wenn allein angegeben, werden alle Erinnerungen für diesen Block über alle Konversationen hinweg gelöscht. Wenn mit conversationId angegeben, werden Erinnerungen für diese spezifische Konversation in diesem Block gelöscht. |
| `blockName` | string | Nein | Blockname. Alternative zu blockId. Wenn allein angegeben, werden alle Erinnerungen für Blöcke mit diesem Namen gelöscht. Wenn mit conversationId angegeben, werden Erinnerungen für diese Konversation in Blöcken mit diesem Namen gelöscht. |
#### Ausgabe
| Parameter | Typ | Beschreibung |
| --------- | ---- | ----------- |
| `success` | boolean | Ob der Speicher erfolgreich gelöscht wurde |
| `success` | boolean | Ob die Erinnerung erfolgreich gelöscht wurde |
| `message` | string | Erfolgs- oder Fehlermeldung |
| `error` | string | Fehlermeldung, falls fehlgeschlagen |
| `error` | string | Fehlermeldung, wenn der Vorgang fehlgeschlagen ist |
## Hinweise

View File

@@ -109,12 +109,12 @@ Lesen Sie die neuesten Nachrichten aus Slack-Kanälen. Rufen Sie den Konversatio
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `authMethod` | string | Nein | Authentifizierungsmethode: oauth oder bot_token |
| `botToken` | string | Nein | Bot-Token für Custom Bot |
| `botToken` | string | Nein | Bot-Token für benutzerdefinierten Bot |
| `channel` | string | Nein | Slack-Kanal, aus dem Nachrichten gelesen werden sollen \(z.B. #general\) |
| `userId` | string | Nein | Benutzer-ID für DM-Konversation \(z.B. U1234567890\) |
| `limit` | number | Nein | Anzahl der abzurufenden Nachrichten \(Standard: 10, max: 15\) |
| `oldest` | string | Nein | Beginn des Zeitbereichs \(Zeitstempel\) |
| `latest` | string | Nein | Ende des Zeitbereichs \(Zeitstempel\) |
| `limit` | number | Nein | Anzahl der abzurufenden Nachrichten \(Standard: 10, max: 100\) |
| `oldest` | string | Nein | Beginn des Zeitraums \(Zeitstempel\) |
| `latest` | string | Nein | Ende des Zeitraums \(Zeitstempel\) |
#### Ausgabe

View File

@@ -47,13 +47,12 @@ Daten aus einer Supabase-Tabelle abfragen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z. B. jdrkgepadsdopsntdlom\) |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
| `table` | string | Ja | Der Name der abzufragenden Supabase-Tabelle |
| `schema` | string | Nein | Datenbankschema für die Abfrage \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `filter` | string | Nein | PostgREST-Filter \(z. B. "id=eq.123"\) |
| `orderBy` | string | Nein | Spalte zum Sortieren \(fügen Sie DESC für absteigende Sortierung hinzu\) |
| `filter` | string | Nein | PostgREST-Filter \(z.B. "id=eq.123"\) |
| `orderBy` | string | Nein | Spalte zum Sortieren \(fügen Sie DESC für absteigend hinzu\) |
| `limit` | number | Nein | Maximale Anzahl der zurückzugebenden Zeilen |
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
| `apiKey` | string | Ja | Ihr Supabase Service-Rolle-Secret-Schlüssel |
#### Ausgabe
@@ -70,9 +69,8 @@ Daten in eine Supabase-Tabelle einfügen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z. B. jdrkgepadsdopsntdlom\) |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
| `table` | string | Ja | Der Name der Supabase-Tabelle, in die Daten eingefügt werden sollen |
| `schema` | string | Nein | Datenbankschema für das Einfügen \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `data` | array | Ja | Die einzufügenden Daten \(Array von Objekten oder ein einzelnes Objekt\) |
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
@@ -91,11 +89,10 @@ Eine einzelne Zeile aus einer Supabase-Tabelle basierend auf Filterkriterien abr
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z. B. jdrkgepadsdopsntdlom\) |
| `table` | string | Ja | Der Name der abzufragenden Supabase-Tabelle |
| `schema` | string | Nein | Datenbankschema für die Abfrage \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `filter` | string | Ja | PostgREST-Filter zum Auffinden der spezifischen Zeile \(z. B. "id=eq.123"\) |
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID (z.B. jdrkgepadsdopsntdlom) |
| `table` | string | Ja | Der Name der Supabase-Tabelle für die Abfrage |
| `filter` | string | Ja | PostgREST-Filter zum Finden der spezifischen Zeile (z.B. "id=eq.123") |
| `apiKey` | string | Ja | Ihr Supabase Service-Role-Secret-Key |
#### Ausgabe
@@ -112,10 +109,9 @@ Zeilen in einer Supabase-Tabelle basierend auf Filterkriterien aktualisieren
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID (z.B. jdrkgepadsdopsntdlom) |
| `table` | string | Ja | Der Name der zu aktualisierenden Supabase-Tabelle |
| `schema` | string | Nein | Datenbankschema für die Aktualisierung \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `filter` | string | Ja | PostgREST-Filter zur Identifizierung der zu aktualisierenden Zeilen \(z.B. "id=eq.123"\) |
| `filter` | string | Ja | PostgREST-Filter zur Identifizierung der zu aktualisierenden Zeilen (z.B. "id=eq.123") |
| `data` | object | Ja | Daten, die in den übereinstimmenden Zeilen aktualisiert werden sollen |
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
@@ -134,10 +130,9 @@ Zeilen aus einer Supabase-Tabelle basierend auf Filterkriterien löschen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID (z.B. jdrkgepadsdopsntdlom) |
| `table` | string | Ja | Der Name der Supabase-Tabelle, aus der gelöscht werden soll |
| `schema` | string | Nein | Datenbankschema für die Löschung \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `filter` | string | Ja | PostgREST-Filter zur Identifizierung der zu löschenden Zeilen \(z.B. "id=eq.123"\) |
| `filter` | string | Ja | PostgREST-Filter zur Identifizierung der zu löschenden Zeilen (z.B. "id=eq.123") |
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
#### Ausgabe
@@ -156,9 +151,8 @@ Daten in eine Supabase-Tabelle einfügen oder aktualisieren (Upsert-Operation)
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
| `table` | string | Ja | Der Name der Supabase-Tabelle, in die Daten eingefügt oder aktualisiert werden sollen |
| `schema` | string | Nein | Datenbankschema für Upsert \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `data` | array | Ja | Die Daten für Upsert \(Einfügen oder Aktualisieren\) - Array von Objekten oder ein einzelnes Objekt |
| `table` | string | Ja | Der Name der Supabase-Tabelle, in die Daten upsertet werden sollen |
| `data` | array | Ja | Die zu upsertenden Daten \(einfügen oder aktualisieren\) - Array von Objekten oder ein einzelnes Objekt |
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
#### Ausgabe
@@ -177,8 +171,7 @@ Zeilen in einer Supabase-Tabelle zählen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
| `table` | string | Ja | Der Name der Supabase-Tabelle, aus der Zeilen gezählt werden sollen |
| `schema` | string | Nein | Datenbankschema zum Zählen \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `table` | string | Ja | Der Name der Supabase-Tabelle, deren Zeilen gezählt werden sollen |
| `filter` | string | Nein | PostgREST-Filter \(z.B. "status=eq.active"\) |
| `countType` | string | Nein | Zähltyp: exact, planned oder estimated \(Standard: exact\) |
| `apiKey` | string | Ja | Ihr Supabase Service Role Secret Key |
@@ -199,8 +192,7 @@ Volltextsuche in einer Supabase-Tabelle durchführen
| Parameter | Typ | Erforderlich | Beschreibung |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Ja | Ihre Supabase-Projekt-ID \(z.B. jdrkgepadsdopsntdlom\) |
| `table` | string | Ja | Der Name der zu durchsuchenden Supabase-Tabelle |
| `schema` | string | Nein | Datenbankschema zum Durchsuchen \(Standard: public\). Verwenden Sie dies, um auf Tabellen in anderen Schemas zuzugreifen. |
| `table` | string | Ja | Der Name der Supabase-Tabelle für die Suche |
| `column` | string | Ja | Die Spalte, in der gesucht werden soll |
| `query` | string | Ja | Die Suchanfrage |
| `searchType` | string | Nein | Suchtyp: plain, phrase oder websearch \(Standard: websearch\) |

View File

@@ -48,40 +48,40 @@ The model breakdown shows:
<Tabs items={['Hosted Models', 'Bring Your Own API Key']}>
<Tab>
**Hosted Models** - Sim provides API keys with a 2x pricing multiplier:
**Hosted Models** - Sim provides API keys with a 2.5x pricing multiplier:
**OpenAI**
| Model | Base Price (Input/Output) | Hosted Price (Input/Output) |
|-------|---------------------------|----------------------------|
| GPT-5.1 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.50 / $4.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.10 / $0.80 |
| GPT-4o | $2.50 / $10.00 | $5.00 / $20.00 |
| GPT-4.1 | $2.00 / $8.00 | $4.00 / $16.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $0.80 / $3.20 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.20 / $0.80 |
| o1 | $15.00 / $60.00 | $30.00 / $120.00 |
| o3 | $2.00 / $8.00 | $4.00 / $16.00 |
| o4 Mini | $1.10 / $4.40 | $2.20 / $8.80 |
| GPT-5.1 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.63 / $5.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.13 / $1.00 |
| GPT-4o | $2.50 / $10.00 | $6.25 / $25.00 |
| GPT-4.1 | $2.00 / $8.00 | $5.00 / $20.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $1.00 / $4.00 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.25 / $1.00 |
| o1 | $15.00 / $60.00 | $37.50 / $150.00 |
| o3 | $2.00 / $8.00 | $5.00 / $20.00 |
| o4 Mini | $1.10 / $4.40 | $2.75 / $11.00 |
**Anthropic**
| Model | Base Price (Input/Output) | Hosted Price (Input/Output) |
|-------|---------------------------|----------------------------|
| Claude Opus 4.5 | $5.00 / $25.00 | $10.00 / $50.00 |
| Claude Opus 4.1 | $15.00 / $75.00 | $30.00 / $150.00 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.00 / $10.00 |
| Claude Opus 4.5 | $5.00 / $25.00 | $12.50 / $62.50 |
| Claude Opus 4.1 | $15.00 / $75.00 | $37.50 / $187.50 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.50 / $12.50 |
**Google**
| Model | Base Price (Input/Output) | Hosted Price (Input/Output) |
|-------|---------------------------|----------------------------|
| Gemini 3 Pro Preview | $2.00 / $12.00 | $4.00 / $24.00 |
| Gemini 2.5 Pro | $1.25 / $10.00 | $2.50 / $20.00 |
| Gemini 2.5 Flash | $0.30 / $2.50 | $0.60 / $5.00 |
| Gemini 3 Pro Preview | $2.00 / $12.00 | $5.00 / $30.00 |
| Gemini 2.5 Pro | $0.15 / $0.60 | $0.38 / $1.50 |
| Gemini 2.5 Flash | $0.15 / $0.60 | $0.38 / $1.50 |
*The 2x multiplier covers infrastructure and API management costs.*
*The 2.5x multiplier covers infrastructure and API management costs.*
</Tab>
<Tab>
@@ -183,7 +183,7 @@ Different subscription plans have different usage limits:
| Plan | Monthly Usage Limit | Rate Limits (per minute) |
|------|-------------------|-------------------------|
| **Free** | $20 | 5 sync, 10 async |
| **Free** | $10 | 5 sync, 10 async |
| **Pro** | $100 | 10 sync, 50 async |
| **Team** | $500 (pooled) | 50 sync, 100 async |
| **Enterprise** | Custom | Custom |

View File

@@ -34,15 +34,9 @@ Once your documents are processed, you can view and edit the individual chunks.
<Image src="/static/knowledgebase/knowledgebase.png" alt="Document chunks view showing processed content" width={800} height={500} />
### Chunk Configuration
When creating a knowledge base, you can configure how documents are split into chunks:
| Setting | Unit | Default | Range | Description |
|---------|------|---------|-------|-------------|
| **Max Chunk Size** | tokens | 1,024 | 100-4,000 | Maximum size of each chunk (1 token ≈ 4 characters) |
| **Min Chunk Size** | characters | 1 | 1-2,000 | Minimum chunk size to avoid tiny fragments |
| **Overlap** | characters | 200 | 0-500 | Context overlap between consecutive chunks |
- **Default chunk size**: 1,024 characters
- **Configurable range**: 100-4,000 characters per chunk
- **Smart overlap**: 200 characters by default for context preservation
- **Hierarchical splitting**: Respects document structure (sections, paragraphs, sentences)
### Editing Capabilities

View File

@@ -41,7 +41,6 @@ Create a new contact in Intercom with email, external_id, or role
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `role` | string | No | The role of the contact. Accepts 'user' or 'lead'. Defaults to 'lead' if not specified. |
| `email` | string | No | The contact's email address |
| `external_id` | string | No | A unique identifier for the contact provided by the client |
| `phone` | string | No | The contact's phone number |
@@ -52,7 +51,6 @@ Create a new contact in Intercom with email, external_id, or role
| `owner_id` | string | No | The id of an admin that has been assigned account ownership of the contact |
| `unsubscribed_from_emails` | boolean | No | Whether the contact is unsubscribed from emails |
| `custom_attributes` | string | No | Custom attributes as JSON object \(e.g., \{"attribute_name": "value"\}\) |
| `company_id` | string | No | Company ID to associate the contact with during creation |
#### Output
@@ -87,8 +85,6 @@ Update an existing contact in Intercom
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | Yes | Contact ID to update |
| `role` | string | No | The role of the contact. Accepts 'user' or 'lead'. |
| `external_id` | string | No | A unique identifier for the contact provided by the client |
| `email` | string | No | The contact's email address |
| `phone` | string | No | The contact's phone number |
| `name` | string | No | The contact's name |
@@ -98,7 +94,6 @@ Update an existing contact in Intercom
| `owner_id` | string | No | The id of an admin that has been assigned account ownership of the contact |
| `unsubscribed_from_emails` | boolean | No | Whether the contact is unsubscribed from emails |
| `custom_attributes` | string | No | Custom attributes as JSON object \(e.g., \{"attribute_name": "value"\}\) |
| `company_id` | string | No | Company ID to associate the contact with |
#### Output
@@ -136,8 +131,6 @@ Search for contacts in Intercom using a query
| `query` | string | Yes | Search query \(e.g., \{"field":"email","operator":"=","value":"user@example.com"\}\) |
| `per_page` | number | No | Number of results per page \(max: 150\) |
| `starting_after` | string | No | Cursor for pagination |
| `sort_field` | string | No | Field to sort by \(e.g., "name", "created_at", "last_seen_at"\) |
| `sort_order` | string | No | Sort order: "ascending" or "descending" |
#### Output
@@ -179,7 +172,6 @@ Create or update a company in Intercom
| `industry` | string | No | The industry the company operates in |
| `monthly_spend` | number | No | How much revenue the company generates for your business. Note: This field truncates floats to whole integers \(e.g., 155.98 becomes 155\) |
| `custom_attributes` | string | No | Custom attributes as JSON object |
| `remote_created_at` | number | No | The time the company was created by you as a Unix timestamp |
#### Output
@@ -215,7 +207,6 @@ List all companies from Intercom with pagination support. Note: This endpoint ha
| --------- | ---- | -------- | ----------- |
| `per_page` | number | No | Number of results per page |
| `page` | number | No | Page number |
| `starting_after` | string | No | Cursor for pagination \(preferred over page-based pagination\) |
#### Output
@@ -234,7 +225,6 @@ Retrieve a single conversation by ID from Intercom
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Yes | Conversation ID to retrieve |
| `display_as` | string | No | Set to "plaintext" to retrieve messages in plain text |
| `include_translations` | boolean | No | When true, conversation parts will be translated to the detected language of the conversation |
#### Output
@@ -253,8 +243,6 @@ List all conversations from Intercom with pagination support
| --------- | ---- | -------- | ----------- |
| `per_page` | number | No | Number of results per page \(max: 150\) |
| `starting_after` | string | No | Cursor for pagination |
| `sort` | string | No | Field to sort by \(e.g., "waiting_since", "updated_at", "created_at"\) |
| `order` | string | No | Sort order: "asc" \(ascending\) or "desc" \(descending\) |
#### Output
@@ -276,7 +264,6 @@ Reply to a conversation as an admin in Intercom
| `body` | string | Yes | The text body of the reply |
| `admin_id` | string | No | The ID of the admin authoring the reply. If not provided, a default admin \(Operator/Fin\) will be used. |
| `attachment_urls` | string | No | Comma-separated list of image URLs \(max 10\) |
| `created_at` | number | No | Unix timestamp for when the reply was created. If not provided, current time is used. |
#### Output
@@ -296,8 +283,6 @@ Search for conversations in Intercom using a query
| `query` | string | Yes | Search query as JSON object |
| `per_page` | number | No | Number of results per page \(max: 150\) |
| `starting_after` | string | No | Cursor for pagination |
| `sort_field` | string | No | Field to sort by \(e.g., "created_at", "updated_at"\) |
| `sort_order` | string | No | Sort order: "ascending" or "descending" |
#### Output
@@ -317,10 +302,6 @@ Create a new ticket in Intercom
| `ticket_type_id` | string | Yes | The ID of the ticket type |
| `contacts` | string | Yes | JSON array of contact identifiers \(e.g., \[\{"id": "contact_id"\}\]\) |
| `ticket_attributes` | string | Yes | JSON object with ticket attributes including _default_title_ and _default_description_ |
| `company_id` | string | No | Company ID to associate the ticket with |
| `created_at` | number | No | Unix timestamp for when the ticket was created. If not provided, current time is used. |
| `conversation_to_link_id` | string | No | ID of an existing conversation to link to this ticket |
| `disable_notifications` | boolean | No | When true, suppresses notifications when the ticket is created |
#### Output
@@ -354,15 +335,13 @@ Create and send a new admin-initiated message in Intercom
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `message_type` | string | Yes | Message type: "inapp" for in-app messages or "email" for email messages |
| `template` | string | Yes | Message template style: "plain" for plain text or "personal" for personalized style |
| `message_type` | string | Yes | Message type: "inapp" or "email" |
| `subject` | string | No | The subject of the message \(for email type\) |
| `body` | string | Yes | The body of the message |
| `from_type` | string | Yes | Sender type: "admin" |
| `from_id` | string | Yes | The ID of the admin sending the message |
| `to_type` | string | Yes | Recipient type: "contact" |
| `to_id` | string | Yes | The ID of the contact receiving the message |
| `created_at` | number | No | Unix timestamp for when the message was created. If not provided, current time is used. |
#### Output

View File

@@ -97,16 +97,10 @@ Write a Jira issue
| `projectId` | string | Yes | Project ID for the issue |
| `summary` | string | Yes | Summary for the issue |
| `description` | string | No | Description for the issue |
| `priority` | string | No | Priority ID or name for the issue \(e.g., "10000" or "High"\) |
| `assignee` | string | No | Assignee account ID for the issue |
| `priority` | string | No | Priority for the issue |
| `assignee` | string | No | Assignee for the issue |
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |
| `issueType` | string | Yes | Type of issue to create \(e.g., Task, Story\) |
| `labels` | array | No | Labels for the issue \(array of label names\) |
| `duedate` | string | No | Due date for the issue \(format: YYYY-MM-DD\) |
| `reporter` | string | No | Reporter account ID for the issue |
| `environment` | string | No | Environment information for the issue |
| `customFieldId` | string | No | Custom field ID \(e.g., customfield_10001\) |
| `customFieldValue` | string | No | Value for the custom field |
#### Output
@@ -116,7 +110,6 @@ Write a Jira issue
| `issueKey` | string | Created issue key \(e.g., PROJ-123\) |
| `summary` | string | Issue summary |
| `url` | string | URL to the created issue |
| `assigneeId` | string | Account ID of the assigned user \(if assigned\) |
### `jira_bulk_read`
@@ -530,30 +523,6 @@ Remove a watcher from a Jira issue
| `issueKey` | string | Issue key |
| `watcherAccountId` | string | Removed watcher account ID |
### `jira_get_users`
Get Jira users. If an account ID is provided, returns a single user. Otherwise, returns a list of all users.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
| `accountId` | string | No | Optional account ID to get a specific user. If not provided, returns all users. |
| `startAt` | number | No | The index of the first user to return \(for pagination, default: 0\) |
| `maxResults` | number | No | Maximum number of users to return \(default: 50\) |
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `ts` | string | Timestamp of the operation |
| `users` | json | Array of users with accountId, displayName, emailAddress, active status, and avatarUrls |
| `total` | number | Total number of users returned |
| `startAt` | number | Pagination start index |
| `maxResults` | number | Maximum results per page |
## Notes

View File

@@ -26,10 +26,11 @@ Add a new memory to the database or append to existing memory with the same ID.
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | No | Conversation identifier \(e.g., user-123, session-abc\). If a memory with this conversationId already exists, the new message will be appended to it. |
| `conversationId` | string | No | Conversation identifier \(e.g., user-123, session-abc\). If a memory with this conversationId already exists for this block, the new message will be appended to it. |
| `id` | string | No | Legacy parameter for conversation identifier. Use conversationId instead. Provided for backwards compatibility. |
| `role` | string | Yes | Role for agent memory \(user, assistant, or system\) |
| `content` | string | Yes | Content for agent memory |
| `blockId` | string | No | Optional block ID. If not provided, uses the current block ID from execution context, or defaults to "default". |
#### Output
@@ -41,21 +42,23 @@ Add a new memory to the database or append to existing memory with the same ID.
### `memory_get`
Retrieve memory by conversationId. Returns matching memories.
Retrieve memory by conversationId, blockId, blockName, or a combination. Returns all matching memories.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | No | Conversation identifier \(e.g., user-123, session-abc\). Returns memories for this conversation. |
| `conversationId` | string | No | Conversation identifier \(e.g., user-123, session-abc\). If provided alone, returns all memories for this conversation across all blocks. |
| `id` | string | No | Legacy parameter for conversation identifier. Use conversationId instead. Provided for backwards compatibility. |
| `blockId` | string | No | Block identifier. If provided alone, returns all memories for this block across all conversations. If provided with conversationId, returns memories for that specific conversation in this block. |
| `blockName` | string | No | Block name. Alternative to blockId. If provided alone, returns all memories for blocks with this name. If provided with conversationId, returns memories for that conversation in blocks with this name. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether the memory was retrieved successfully |
| `memories` | array | Array of memory objects with conversationId and data fields |
| `memories` | array | Array of memory objects with conversationId, blockId, blockName, and data fields |
| `message` | string | Success or error message |
| `error` | string | Error message if operation failed |
@@ -73,20 +76,22 @@ Retrieve all memories from the database
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `success` | boolean | Whether all memories were retrieved successfully |
| `memories` | array | Array of all memory objects with key, conversationId, and data fields |
| `memories` | array | Array of all memory objects with key, conversationId, blockId, blockName, and data fields |
| `message` | string | Success or error message |
| `error` | string | Error message if operation failed |
### `memory_delete`
Delete memories by conversationId.
Delete memories by conversationId, blockId, blockName, or a combination. Supports bulk deletion.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | No | Conversation identifier \(e.g., user-123, session-abc\). Deletes all memories for this conversation. |
| `conversationId` | string | No | Conversation identifier \(e.g., user-123, session-abc\). If provided alone, deletes all memories for this conversation across all blocks. |
| `id` | string | No | Legacy parameter for conversation identifier. Use conversationId instead. Provided for backwards compatibility. |
| `blockId` | string | No | Block identifier. If provided alone, deletes all memories for this block across all conversations. If provided with conversationId, deletes memories for that specific conversation in this block. |
| `blockName` | string | No | Block name. Alternative to blockId. If provided alone, deletes all memories for blocks with this name. If provided with conversationId, deletes memories for that conversation in blocks with this name. |
#### Output

View File

@@ -114,7 +114,7 @@ Read the latest messages from Slack channels. Retrieve conversation history with
| `botToken` | string | No | Bot token for Custom Bot |
| `channel` | string | No | Slack channel to read messages from \(e.g., #general\) |
| `userId` | string | No | User ID for DM conversation \(e.g., U1234567890\) |
| `limit` | number | No | Number of messages to retrieve \(default: 10, max: 15\) |
| `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\) |

View File

@@ -52,7 +52,6 @@ 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 |
| `schema` | string | No | Database schema to query from \(default: public\). Use this to access tables in other schemas. |
| `filter` | string | No | PostgREST filter \(e.g., "id=eq.123"\) |
| `orderBy` | string | No | Column to order by \(add DESC for descending\) |
| `limit` | number | No | Maximum number of rows to return |
@@ -75,7 +74,6 @@ 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 |
| `schema` | string | No | Database schema to insert into \(default: public\). Use this to access tables in other schemas. |
| `data` | array | Yes | The data to insert \(array of objects or a single object\) |
| `apiKey` | string | Yes | Your Supabase service role secret key |
@@ -96,7 +94,6 @@ Get a single row from a Supabase table based on filter criteria
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `table` | string | Yes | The name of the Supabase table to query |
| `schema` | string | No | Database schema to query from \(default: public\). Use this to access tables in other schemas. |
| `filter` | string | Yes | PostgREST filter to find the specific row \(e.g., "id=eq.123"\) |
| `apiKey` | string | Yes | Your Supabase service role secret key |
@@ -117,7 +114,6 @@ Update rows in a Supabase table based on filter criteria
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `table` | string | Yes | The name of the Supabase table to update |
| `schema` | string | No | Database schema to update in \(default: public\). Use this to access tables in other schemas. |
| `filter` | string | Yes | PostgREST filter to identify rows to update \(e.g., "id=eq.123"\) |
| `data` | object | Yes | Data to update in the matching rows |
| `apiKey` | string | Yes | Your Supabase service role secret key |
@@ -139,7 +135,6 @@ Delete rows from a Supabase table based on filter criteria
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `table` | string | Yes | The name of the Supabase table to delete from |
| `schema` | string | No | Database schema to delete from \(default: public\). Use this to access tables in other schemas. |
| `filter` | string | Yes | PostgREST filter to identify rows to delete \(e.g., "id=eq.123"\) |
| `apiKey` | string | Yes | Your Supabase service role secret key |
@@ -160,7 +155,6 @@ Insert or update data in a Supabase table (upsert operation)
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `table` | string | Yes | The name of the Supabase table to upsert data into |
| `schema` | string | No | Database schema to upsert into \(default: public\). Use this to access tables in other schemas. |
| `data` | array | Yes | The data to upsert \(insert or update\) - array of objects or a single object |
| `apiKey` | string | Yes | Your Supabase service role secret key |
@@ -181,7 +175,6 @@ Count rows in a Supabase table
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `table` | string | Yes | The name of the Supabase table to count rows from |
| `schema` | string | No | Database schema to count from \(default: public\). Use this to access tables in other schemas. |
| `filter` | string | No | PostgREST filter \(e.g., "status=eq.active"\) |
| `countType` | string | No | Count type: exact, planned, or estimated \(default: exact\) |
| `apiKey` | string | Yes | Your Supabase service role secret key |
@@ -203,7 +196,6 @@ Perform full-text search on a Supabase table
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Yes | Your Supabase project ID \(e.g., jdrkgepadsdopsntdlom\) |
| `table` | string | Yes | The name of the Supabase table to search |
| `schema` | string | No | Database schema to search in \(default: public\). Use this to access tables in other schemas. |
| `column` | string | Yes | The column to search in |
| `query` | string | Yes | The search query |
| `searchType` | string | No | Search type: plain, phrase, or websearch \(default: websearch\) |

View File

@@ -47,42 +47,42 @@ El desglose del modelo muestra:
## Opciones de precios
<Tabs items={['Modelos alojados', 'Trae tu propia clave API']}>
<Tabs items={['Hosted Models', 'Bring Your Own API Key']}>
<Tab>
**Modelos alojados** - Sim proporciona claves API con un multiplicador de precio de 2x:
**Modelos alojados** - Sim proporciona claves API con un multiplicador de precio de 2.5x:
**OpenAI**
| Modelo | Precio base (entrada/salida) | Precio alojado (entrada/salida) |
| Modelo | Precio base (Entrada/Salida) | Precio alojado (Entrada/Salida) |
|-------|---------------------------|----------------------------|
| GPT-5.1 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.50 / $4.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.10 / $0.80 |
| GPT-4o | $2.50 / $10.00 | $5.00 / $20.00 |
| GPT-4.1 | $2.00 / $8.00 | $4.00 / $16.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $0.80 / $3.20 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.20 / $0.80 |
| o1 | $15.00 / $60.00 | $30.00 / $120.00 |
| o3 | $2.00 / $8.00 | $4.00 / $16.00 |
| o4 Mini | $1.10 / $4.40 | $2.20 / $8.80 |
| GPT-5.1 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.63 / $5.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.13 / $1.00 |
| GPT-4o | $2.50 / $10.00 | $6.25 / $25.00 |
| GPT-4.1 | $2.00 / $8.00 | $5.00 / $20.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $1.00 / $4.00 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.25 / $1.00 |
| o1 | $15.00 / $60.00 | $37.50 / $150.00 |
| o3 | $2.00 / $8.00 | $5.00 / $20.00 |
| o4 Mini | $1.10 / $4.40 | $2.75 / $11.00 |
**Anthropic**
| Modelo | Precio base (entrada/salida) | Precio alojado (entrada/salida) |
| Modelo | Precio base (Entrada/Salida) | Precio alojado (Entrada/Salida) |
|-------|---------------------------|----------------------------|
| Claude Opus 4.5 | $5.00 / $25.00 | $10.00 / $50.00 |
| Claude Opus 4.1 | $15.00 / $75.00 | $30.00 / $150.00 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.00 / $10.00 |
| Claude Opus 4.5 | $5.00 / $25.00 | $12.50 / $62.50 |
| Claude Opus 4.1 | $15.00 / $75.00 | $37.50 / $187.50 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.50 / $12.50 |
**Google**
| Modelo | Precio base (entrada/salida) | Precio alojado (entrada/salida) |
| Modelo | Precio base (Entrada/Salida) | Precio alojado (Entrada/Salida) |
|-------|---------------------------|----------------------------|
| Gemini 3 Pro Preview | $2.00 / $12.00 | $4.00 / $24.00 |
| Gemini 2.5 Pro | $1.25 / $10.00 | $2.50 / $20.00 |
| Gemini 2.5 Flash | $0.30 / $2.50 | $0.60 / $5.00 |
| Gemini 3 Pro Preview | $2.00 / $12.00 | $5.00 / $30.00 |
| Gemini 2.5 Pro | $0.15 / $0.60 | $0.38 / $1.50 |
| Gemini 2.5 Flash | $0.15 / $0.60 | $0.38 / $1.50 |
*El multiplicador 2x cubre los costos de infraestructura y gestión de API.*
*El multiplicador de 2.5x cubre los costos de infraestructura y gestión de API.*
</Tab>
<Tab>
@@ -187,10 +187,10 @@ Los diferentes planes de suscripción tienen diferentes límites de uso:
| Plan | Límite de uso mensual | Límites de tasa (por minuto) |
|------|-------------------|-------------------------|
| **Gratis** | $20 | 5 síncronas, 10 asíncronas |
| **Pro** | $100 | 10 síncronas, 50 asíncronas |
| **Equipo** | $500 (compartido) | 50 síncronas, 100 asíncronas |
| **Empresarial** | Personalizado | Personalizado |
| **Gratuito** | $10 | 5 sincrónico, 10 asincrónico |
| **Pro** | $100 | 10 sincrónico, 50 asincrónico |
| **Equipo** | $500 (agrupado) | 50 sincrónico, 100 asincrónico |
| **Empresa** | Personalizado | Personalizado |
## Modelo de facturación

View File

@@ -35,87 +35,81 @@ Una vez que tus documentos están procesados, puedes ver y editar los fragmentos
<Image src="/static/knowledgebase/knowledgebase.png" alt="Vista de fragmentos de documentos mostrando contenido procesado" width={800} height={500} />
### Configuración de fragmentos
Al crear una base de conocimiento, puedes configurar cómo se dividen los documentos en fragmentos:
| Configuración | Unidad | Predeterminado | Rango | Descripción |
|---------|------|---------|-------|-------------|
| **Tamaño máximo de fragmento** | tokens | 1.024 | 100-4.000 | Tamaño máximo de cada fragmento (1 token ≈ 4 caracteres) |
| **Tamaño mínimo de fragmento** | caracteres | 1 | 1-2.000 | Tamaño mínimo de fragmento para evitar fragmentos diminutos |
| **Superposición** | caracteres | 200 | 0-500 | Superposición de contexto entre fragmentos consecutivos |
- **División jerárquica**: respeta la estructura del documento (secciones, párrafos, oraciones)
- **Tamaño predeterminado del fragmento**: 1.024 caracteres
- **Rango configurable**: 100-4.000 caracteres por fragmento
- **Superposición inteligente**: 200 caracteres por defecto para preservar el contexto
- **División jerárquica**: Respeta la estructura del documento (secciones, párrafos, oraciones)
### Capacidades de edición
- **Editar contenido de fragmentos**: modifica el contenido de texto de fragmentos individuales
- **Ajustar límites de fragmentos**: combina o divide fragmentos según sea necesario
- **Añadir metadatos**: mejora los fragmentos con contexto adicional
- **Operaciones masivas**: gestiona múltiples fragmentos de manera eficiente
- **Editar contenido de fragmentos**: Modificar el contenido de texto de fragmentos individuales
- **Ajustar límites de fragmentos**: Fusionar o dividir fragmentos según sea necesario
- **Añadir metadatos**: Mejorar fragmentos con contexto adicional
- **Operaciones masivas**: Gestionar múltiples fragmentos de manera eficiente
## Procesamiento avanzado de PDF
Para documentos PDF, Sim ofrece capacidades de procesamiento mejoradas:
### Compatibilidad con OCR
### Soporte OCR
Cuando se configura con Azure o [Mistral OCR](https://docs.mistral.ai/ocr/):
- **Procesamiento de documentos escaneados**: extrae texto de PDF basados en imágenes
- **Manejo de contenido mixto**: procesa PDF con texto e imágenes
- **Alta precisión**: los modelos de IA avanzados garantizan una extracción de texto precisa
- **Procesamiento de documentos escaneados**: Extraer texto de PDFs basados en imágenes
- **Manejo de contenido mixto**: Procesar PDFs con texto e imágenes
- **Alta precisión**: Modelos avanzados de IA aseguran una extracción precisa del texto
## Uso del bloque de conocimiento en flujos de trabajo
Una vez que tus documentos estén procesados, puedes usarlos en tus flujos de trabajo de IA a través del bloque de conocimiento. Esto habilita la generación aumentada por recuperación (RAG), permitiendo que tus agentes de IA accedan y razonen sobre el contenido de tus documentos para proporcionar respuestas más precisas y contextuales.
Una vez que tus documentos son procesados, puedes utilizarlos en tus flujos de trabajo de IA a través del bloque de Conocimiento. Esto permite la Generación Aumentada por Recuperación (RAG), permitiendo a tus agentes de IA acceder y razonar sobre el contenido de tus documentos para proporcionar respuestas más precisas y contextuales.
<Image src="/static/knowledgebase/knowledgebase-2.png" alt="Uso del bloque de conocimiento en flujos de trabajo" width={800} height={500} />
### Características del bloque de conocimiento
- **Búsqueda semántica**: encuentra contenido relevante usando consultas en lenguaje natural
- **Integración de contexto**: incluye automáticamente fragmentos relevantes en las indicaciones del agente
- **Recuperación dinámica**: la búsqueda ocurre en tiempo real durante la ejecución del flujo de trabajo
- **Puntuación de relevancia**: resultados clasificados por similitud semántica
- **Búsqueda semántica**: Encontrar contenido relevante usando consultas en lenguaje natural
- **Integración de contexto**: Incluir automáticamente fragmentos relevantes en los prompts del agente
- **Recuperación dinámica**: La búsqueda ocurre en tiempo real durante la ejecución del flujo de trabajo
- **Puntuación de relevancia**: Resultados clasificados por similitud semántica
### Opciones de integración
- **Prompts del sistema**: proporciona contexto a tus agentes de IA
- **Contexto dinámico**: busca e incluye información relevante durante las conversaciones
- **Búsqueda multidocumento**: consulta en toda tu base de conocimiento
- **Búsqueda filtrada**: combina con etiquetas para una recuperación precisa de contenido
- **Prompts del sistema**: Proporcionar contexto a tus agentes de IA
- **Contexto dinámico**: Buscar e incluir información relevante durante las conversaciones
- **Búsqueda multi-documento**: Consultar a través de toda tu base de conocimiento
- **Búsqueda filtrada**: Combinar con etiquetas para una recuperación precisa de contenido
## Tecnología de búsqueda vectorial
Sim utiliza búsqueda vectorial impulsada por [pgvector](https://github.com/pgvector/pgvector) para comprender el significado y contexto de tu contenido:
Sim utiliza búsqueda vectorial impulsada por [pgvector](https://github.com/pgvector/pgvector) para entender el significado y contexto de tu contenido:
### Comprensión semántica
- **Búsqueda contextual**: encuentra contenido relevante incluso cuando las palabras clave exactas no coinciden
- **Recuperación basada en conceptos**: comprende las relaciones entre ideas
- **Soporte multiidioma**: funciona en diferentes idiomas
- **Reconocimiento de sinónimos**: encuentra términos y conceptos relacionados
- **Búsqueda contextual**: Encuentra contenido relevante incluso cuando las palabras clave exactas no coinciden
- **Recuperación basada en conceptos**: Comprende las relaciones entre ideas
- **Soporte multilingüe**: Funciona en diferentes idiomas
- **Reconocimiento de sinónimos**: Encuentra términos y conceptos relacionados
### Capacidades de búsqueda
- **Consultas en lenguaje natural**: haz preguntas en lenguaje cotidiano
- **Búsqueda por similitud**: encuentra contenido conceptualmente similar
- **Búsqueda híbrida**: combina búsqueda vectorial y búsqueda tradicional por palabras clave
- **Resultados configurables**: controla el número y el umbral de relevancia de los resultados
- **Consultas en lenguaje natural**: Haz preguntas en español simple
- **Búsqueda por similitud**: Encuentra contenido conceptualmente similar
- **Búsqueda híbrida**: Combina búsqueda vectorial y tradicional por palabras clave
- **Resultados configurables**: Controla el número y umbral de relevancia de los resultados
## Gestión de documentos
### Funciones de organización
- **Carga masiva**: sube múltiples archivos a la vez mediante la API asíncrona
- **Estado de procesamiento**: actualizaciones en tiempo real sobre el procesamiento de documentos
- **Búsqueda y filtrado**: encuentra documentos rápidamente en colecciones grandes
- **Seguimiento de metadatos**: captura automática de información de archivos y detalles de procesamiento
### Características de organización
- **Carga masiva**: Sube múltiples archivos a la vez mediante la API asíncrona
- **Estado de procesamiento**: Actualizaciones en tiempo real sobre el procesamiento de documentos
- **Búsqueda y filtrado**: Encuentra documentos rápidamente en grandes colecciones
- **Seguimiento de metadatos**: Captura automática de información de archivos y detalles de procesamiento
### Seguridad y privacidad
- **Almacenamiento seguro**: documentos almacenados con seguridad de nivel empresarial
- **Control de acceso**: permisos basados en el espacio de trabajo
- **Aislamiento de procesamiento**: cada espacio de trabajo tiene procesamiento de documentos aislado
- **Retención de datos**: configura políticas de retención de documentos
- **Almacenamiento seguro**: Documentos almacenados con seguridad de nivel empresarial
- **Control de acceso**: Permisos basados en espacios de trabajo
- **Aislamiento de procesamiento**: Cada espacio de trabajo tiene procesamiento de documentos aislado
- **Retención de datos**: Configura políticas de retención de documentos
## Primeros pasos
1. **Navega a tu base de conocimiento**: accede desde la barra lateral de tu espacio de trabajo
2. **Sube documentos**: arrastra y suelta o selecciona archivos para subir
3. **Monitorea el procesamiento**: observa cómo se procesan y fragmentan los documentos
4. **Explora fragmentos**: visualiza y edita el contenido procesado
5. **Añade a flujos de trabajo**: utiliza el bloque Knowledge para integrar con tus agentes de IA
1. **Navega a tu base de conocimiento**: Accede desde la barra lateral de tu espacio de trabajo
2. **Sube documentos**: Arrastra y suelta o selecciona archivos para subir
3. **Monitorea el procesamiento**: Observa cómo se procesan y dividen los documentos
4. **Explora fragmentos**: Visualiza y edita el contenido procesado
5. **Añade a flujos de trabajo**: Usa el bloque de Conocimiento para integrarlo con tus agentes de IA
La base de conocimientos transforma tus documentos estáticos en un recurso inteligente y consultable que tus flujos de trabajo de IA pueden aprovechar para obtener respuestas más informadas y contextuales.
La base de conocimiento transforma tus documentos estáticos en un recurso inteligente y consultable que tus flujos de trabajo de IA pueden aprovechar para obtener respuestas más informadas y contextuales.

View File

@@ -38,18 +38,16 @@ Crear un nuevo contacto en Intercom con email, external_id o rol
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `role` | string | No | El rol del contacto. Acepta 'user' o 'lead'. Por defecto es 'lead' si no se especifica. |
| `email` | string | No | La dirección de correo electrónico del contacto |
| `external_id` | string | No | Un identificador único para el contacto proporcionado por el cliente |
| `phone` | string | No | El número de teléfono del contacto |
| `name` | string | No | El nombre del contacto |
| `avatar` | string | No | Una URL de imagen de avatar para el contacto |
| `signed_up_at` | number | No | La hora en que el usuario se registró como marca de tiempo Unix |
| `last_seen_at` | number | No | La hora en que el usuario fue visto por última vez como marca de tiempo Unix |
| `owner_id` | string | No | El id de un administrador al que se le ha asignado la propiedad de la cuenta del contacto |
| `signed_up_at` | number | No | El momento en que el usuario se registró como marca de tiempo Unix |
| `last_seen_at` | number | No | El momento en que el usuario fue visto por última vez como marca de tiempo Unix |
| `owner_id` | string | No | El id de un administrador que ha sido asignado como propietario de la cuenta del contacto |
| `unsubscribed_from_emails` | boolean | No | Si el contacto está dado de baja de los correos electrónicos |
| `custom_attributes` | string | No | Atributos personalizados como objeto JSON \(ej., \{"attribute_name": "value"\}\) |
| `company_id` | string | No | ID de empresa para asociar el contacto durante la creación |
| `custom_attributes` | string | No | Atributos personalizados como objeto JSON \(p. ej., \{"nombre_atributo": "valor"\}\) |
#### Salida
@@ -84,18 +82,15 @@ Actualizar un contacto existente en Intercom
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | Sí | ID del contacto a actualizar |
| `role` | string | No | El rol del contacto. Acepta 'user' o 'lead'. |
| `external_id` | string | No | Un identificador único para el contacto proporcionado por el cliente |
| `email` | string | No | La dirección de correo electrónico del contacto |
| `phone` | string | No | El número de teléfono del contacto |
| `name` | string | No | El nombre del contacto |
| `avatar` | string | No | Una URL de imagen de avatar para el contacto |
| `signed_up_at` | number | No | La hora en que el usuario se registró como marca de tiempo Unix |
| `last_seen_at` | number | No | La hora en que el usuario fue visto por última vez como marca de tiempo Unix |
| `owner_id` | string | No | El id de un administrador al que se le ha asignado la propiedad de la cuenta del contacto |
| `email` | string | No | Dirección de correo electrónico del contacto |
| `phone` | string | No | Número de teléfono del contacto |
| `name` | string | No | Nombre del contacto |
| `avatar` | string | No | URL de imagen de avatar para el contacto |
| `signed_up_at` | number | No | El momento en que el usuario se registró como marca de tiempo Unix |
| `last_seen_at` | number | No | El momento en que el usuario fue visto por última vez como marca de tiempo Unix |
| `owner_id` | string | No | El id de un administrador que ha sido asignado como propietario de la cuenta del contacto |
| `unsubscribed_from_emails` | boolean | No | Si el contacto está dado de baja de los correos electrónicos |
| `custom_attributes` | string | No | Atributos personalizados como objeto JSON \(ej., \{"attribute_name": "value"\}\) |
| `company_id` | string | No | ID de empresa para asociar el contacto |
| `custom_attributes` | string | No | Atributos personalizados como objeto JSON (p. ej., \{"nombre_atributo": "valor"\}) |
#### Salida
@@ -130,11 +125,9 @@ Buscar contactos en Intercom usando una consulta
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `query` | string | Sí | Consulta de búsqueda \(ej., \{"field":"email","operator":"=","value":"user@example.com"\}\) |
| `query` | string | Sí | Consulta de búsqueda \(p. ej., \{"field":"email","operator":"=","value":"user@example.com"\}\) |
| `per_page` | number | No | Número de resultados por página \(máx: 150\) |
| `starting_after` | string | No | Cursor para paginación |
| `sort_field` | string | No | Campo por el cual ordenar \(ej., "name", "created_at", "last_seen_at"\) |
| `sort_order` | string | No | Orden de clasificación: "ascending" o "descending" |
#### Salida
@@ -173,10 +166,9 @@ Crear o actualizar una empresa en Intercom
| `website` | string | No | El sitio web de la empresa |
| `plan` | string | No | El nombre del plan de la empresa |
| `size` | number | No | El número de empleados en la empresa |
| `industry` | string | No | La industria en la que opera la empresa |
| `monthly_spend` | number | No | Cuántos ingresos genera la empresa para tu negocio. Nota: Este campo trunca decimales a números enteros \(ej., 155.98 se convierte en 155\) |
| `industry` | string | No | El sector en el que opera la empresa |
| `monthly_spend` | number | No | Cuántos ingresos genera la empresa para tu negocio. Nota: Este campo trunca los decimales a números enteros \(por ejemplo, 155.98 se convierte en 155\) |
| `custom_attributes` | string | No | Atributos personalizados como objeto JSON |
| `remote_created_at` | number | No | La fecha en que creaste la empresa como marca de tiempo Unix |
#### Salida
@@ -212,7 +204,6 @@ Lista todas las empresas de Intercom con soporte de paginación. Nota: Este endp
| --------- | ---- | -------- | ----------- |
| `per_page` | number | No | Número de resultados por página |
| `page` | number | No | Número de página |
| `starting_after` | string | No | Cursor para paginación \(preferido sobre paginación basada en páginas\) |
#### Salida
@@ -230,8 +221,7 @@ Recuperar una sola conversación por ID desde Intercom
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Sí | ID de la conversación a recuperar |
| `display_as` | string | No | Establecer en "plaintext" para recuperar mensajes en texto plano |
| `include_translations` | boolean | No | Cuando es true, las partes de la conversación se traducirán al idioma detectado de la conversación |
| `display_as` | string | No | Establecer como "plaintext" para recuperar mensajes en texto plano |
#### Salida
@@ -250,8 +240,6 @@ Listar todas las conversaciones de Intercom con soporte de paginación
| --------- | ---- | -------- | ----------- |
| `per_page` | number | No | Número de resultados por página \(máx: 150\) |
| `starting_after` | string | No | Cursor para paginación |
| `sort` | string | No | Campo por el que ordenar \(p. ej., "waiting_since", "updated_at", "created_at"\) |
| `order` | string | No | Orden de clasificación: "asc" \(ascendente\) o "desc" \(descendente\) |
#### Salida
@@ -270,10 +258,9 @@ Responder a una conversación como administrador en Intercom
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | Sí | ID de la conversación a la que responder |
| `message_type` | string | Sí | Tipo de mensaje: "comment" o "note" |
| `body` | string | Sí | El cuerpo de texto de la respuesta |
| `admin_id` | string | No | El ID del administrador que escribe la respuesta. Si no se proporciona, se usará un administrador predeterminado \(Operator/Fin\). |
| `attachment_urls` | string | No | Lista de URLs de imágenes separadas por comas \(máx 10\) |
| `created_at` | number | No | Marca de tiempo Unix de cuándo se creó la respuesta. Si no se proporciona, se usa la hora actual. |
| `body` | string | Sí | El texto del cuerpo de la respuesta |
| `admin_id` | string | No | El ID del administrador que escribe la respuesta. Si no se proporciona, se utilizará un administrador predeterminado \(Operator/Fin\). |
| `attachment_urls` | string | No | Lista separada por comas de URLs de imágenes \(máximo 10\) |
#### Salida
@@ -291,10 +278,8 @@ Buscar conversaciones en Intercom usando una consulta
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `query` | string | Sí | Consulta de búsqueda como objeto JSON |
| `per_page` | number | No | Número de resultados por página \(máx: 150\) |
| `per_page` | number | No | Número de resultados por página (máx: 150) |
| `starting_after` | string | No | Cursor para paginación |
| `sort_field` | string | No | Campo por el que ordenar \(p. ej., "created_at", "updated_at"\) |
| `sort_order` | string | No | Orden de clasificación: "ascending" o "descending" |
#### Salida
@@ -312,12 +297,8 @@ Crear un nuevo ticket en Intercom
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `ticket_type_id` | string | Sí | El ID del tipo de ticket |
| `contacts` | string | Sí | Array JSON de identificadores de contacto \(p. ej., \[\{"id": "contact_id"\}\]\) |
| `contacts` | string | Sí | Array JSON de identificadores de contacto (p. ej., \{"id": "contact_id"\}) |
| `ticket_attributes` | string | Sí | Objeto JSON con atributos del ticket incluyendo _default_title_ y _default_description_ |
| `company_id` | string | No | ID de la empresa para asociar el ticket |
| `created_at` | number | No | Marca de tiempo Unix de cuándo se creó el ticket. Si no se proporciona, se utiliza la hora actual. |
| `conversation_to_link_id` | string | No | ID de una conversación existente para vincular a este ticket |
| `disable_notifications` | boolean | No | Cuando es true, suprime las notificaciones cuando se crea el ticket |
#### Salida
@@ -351,15 +332,13 @@ Crear y enviar un nuevo mensaje iniciado por el administrador en Intercom
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `message_type` | string | Sí | Tipo de mensaje: "inapp" para mensajes dentro de la aplicación o "email" para mensajes de correo electrónico |
| `template` | string | Sí | Estilo de plantilla del mensaje: "plain" para texto sin formato o "personal" para estilo personalizado |
| `message_type` | string | Sí | Tipo de mensaje: "inapp" o "email" |
| `subject` | string | No | El asunto del mensaje \(para tipo email\) |
| `body` | string | Sí | El cuerpo del mensaje |
| `from_type` | string | Sí | Tipo de remitente: "admin" |
| `from_id` | string | Sí | El ID del administrador que envía el mensaje |
| `to_type` | string | Sí | Tipo de destinatario: "contact" |
| `to_id` | string | Sí | El ID del contacto que recibe el mensaje |
| `created_at` | number | No | Marca de tiempo Unix de cuándo se creó el mensaje. Si no se proporciona, se utiliza la hora actual. |
#### Salida

View File

@@ -89,31 +89,24 @@ Escribir una incidencia de Jira
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Sí | Tu dominio de Jira \(p. ej., tuempresa.atlassian.net\) |
| `projectId` | string | Sí | ID del proyecto para la incidencia |
| `summary` | string | Sí | Resumen de la incidencia |
| `description` | string | No | Descripción de la incidencia |
| `priority` | string | No | ID o nombre de prioridad para la incidencia \(p. ej., "10000" o "Alta"\) |
| `assignee` | string | No | ID de cuenta del asignado para la incidencia |
| `cloudId` | string | No | ID de Jira Cloud para la instancia. Si no se proporciona, se obtendrá usando el dominio. |
| `priority` | string | No | Prioridad de la incidencia |
| `assignee` | string | No | Asignado para la incidencia |
| `cloudId` | string | No | ID de Jira Cloud para la instancia. Si no se proporciona, se obtendrá utilizando el dominio. |
| `issueType` | string | Sí | Tipo de incidencia a crear \(p. ej., Tarea, Historia\) |
| `labels` | array | No | Etiquetas para la incidencia \(array de nombres de etiquetas\) |
| `duedate` | string | No | Fecha de vencimiento para la incidencia \(formato: AAAA-MM-DD\) |
| `reporter` | string | No | ID de cuenta del informador para la incidencia |
| `environment` | string | No | Información del entorno para la incidencia |
| `customFieldId` | string | No | ID del campo personalizado \(p. ej., customfield_10001\) |
| `customFieldValue` | string | No | Valor para el campo personalizado |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `ts` | string | Marca de tiempo de la operación |
| `issueKey` | string | Clave de la incidencia creada \(p. ej., PROJ-123\) |
| `issueKey` | string | Clave de la incidencia creada (p. ej., PROJ-123) |
| `summary` | string | Resumen de la incidencia |
| `url` | string | URL de la incidencia creada |
| `assigneeId` | string | ID de cuenta del usuario asignado \(si está asignado\) |
### `jira_bulk_read`
@@ -527,30 +520,6 @@ Eliminar un observador de una incidencia de Jira
| `issueKey` | string | Clave de incidencia |
| `watcherAccountId` | string | ID de cuenta del observador eliminado |
### `jira_get_users`
Obtener usuarios de Jira. Si se proporciona un ID de cuenta, devuelve un solo usuario. De lo contrario, devuelve una lista de todos los usuarios.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `domain` | string | Sí | Tu dominio de Jira \(p. ej., tuempresa.atlassian.net\) |
| `accountId` | string | No | ID de cuenta opcional para obtener un usuario específico. Si no se proporciona, devuelve todos los usuarios. |
| `startAt` | number | No | El índice del primer usuario a devolver \(para paginación, predeterminado: 0\) |
| `maxResults` | number | No | Número máximo de usuarios a devolver \(predeterminado: 50\) |
| `cloudId` | string | No | ID de Jira Cloud para la instancia. Si no se proporciona, se obtendrá usando el dominio. |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `ts` | string | Marca de tiempo de la operación |
| `users` | json | Array de usuarios con accountId, displayName, emailAddress, estado activo y avatarUrls |
| `total` | number | Número total de usuarios devueltos |
| `startAt` | number | Índice de inicio de paginación |
| `maxResults` | number | Máximo de resultados por página |
## Notas
- Categoría: `tools`

View File

@@ -12,54 +12,57 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
## Instrucciones de uso
Integra memoria en el flujo de trabajo. Puede añadir, obtener una memoria, obtener todas las memorias y eliminar memorias.
Integra la Memoria en el flujo de trabajo. Puede añadir, obtener una memoria, obtener todas las memorias y eliminar memorias.
## Herramientas
### `memory_add`
Añadir una nueva memoria a la base de datos o agregar a una memoria existente con el mismo ID.
Añade una nueva memoria a la base de datos o agrega a una memoria existente con el mismo ID.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `conversationId` | string | No | Identificador de conversación \(ej., user-123, session-abc\). Si ya existe una memoria con este conversationId, el nuevo mensaje se agregará a ella. |
| `id` | string | No | Parámetro heredado para identificador de conversación. Usa conversationId en su lugar. Proporcionado para compatibilidad con versiones anteriores. |
| `role` | string | Sí | Rol para la memoria del agente \(user, assistant o system\) |
| `conversationId` | string | No | Identificador de conversación (p. ej., user-123, session-abc). Si ya existe una memoria con este conversationId para este bloque, el nuevo mensaje se añadirá a ella. |
| `id` | string | No | Parámetro heredado para el identificador de conversación. Use conversationId en su lugar. Proporcionado para compatibilidad con versiones anteriores. |
| `role` | string | Sí | Rol para la memoria del agente (user, assistant o system) |
| `content` | string | Sí | Contenido para la memoria del agente |
| `blockId` | string | No | ID de bloque opcional. Si no se proporciona, utiliza el ID del bloque actual del contexto de ejecución, o por defecto "default". |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Si la memoria se añadió correctamente |
| `success` | boolean | Indica si la memoria se añadió correctamente |
| `memories` | array | Array de objetos de memoria incluyendo la memoria nueva o actualizada |
| `error` | string | Mensaje de error si la operación falló |
### `memory_get`
Recuperar memoria por conversationId. Devuelve las memorias coincidentes.
Recuperar memoria por conversationId, blockId, blockName o una combinación. Devuelve todas las memorias coincidentes.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `conversationId` | string | No | Identificador de conversación \(ej., user-123, session-abc\). Devuelve las memorias para esta conversación. |
| `id` | string | No | Parámetro heredado para identificador de conversación. Usa conversationId en su lugar. Proporcionado para compatibilidad con versiones anteriores. |
| `conversationId` | string | No | Identificador de conversación (p. ej., user-123, session-abc). Si se proporciona solo, devuelve todas las memorias para esta conversación en todos los bloques. |
| `id` | string | No | Parámetro heredado para el identificador de conversación. Use conversationId en su lugar. Proporcionado para compatibilidad con versiones anteriores. |
| `blockId` | string | No | Identificador de bloque. Si se proporciona solo, devuelve todas las memorias para este bloque en todas las conversaciones. Si se proporciona con conversationId, devuelve las memorias para esa conversación específica en este bloque. |
| `blockName` | string | No | Nombre del bloque. Alternativa a blockId. Si se proporciona solo, devuelve todas las memorias para bloques con este nombre. Si se proporciona con conversationId, devuelve las memorias para esa conversación en bloques con este nombre. |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Si la memoria se recuperó exitosamente |
| `memories` | array | Array de objetos de memoria con campos conversationId y data |
| `success` | boolean | Si la memoria fue recuperada con éxito |
| `memories` | array | Array de objetos de memoria con campos conversationId, blockId, blockName y data |
| `message` | string | Mensaje de éxito o error |
| `error` | string | Mensaje de error si la operación falló |
### `memory_get_all`
Recupera todas las memorias de la base de datos
Recuperar todas las memorias de la base de datos
#### Entrada
@@ -70,27 +73,29 @@ Recupera todas las memorias de la base de datos
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Si todas las memorias se recuperaron exitosamente |
| `memories` | array | Array de todos los objetos de memoria con campos key, conversationId y data |
| `success` | boolean | Si todas las memorias fueron recuperadas con éxito |
| `memories` | array | Array de todos los objetos de memoria con campos key, conversationId, blockId, blockName y data |
| `message` | string | Mensaje de éxito o error |
| `error` | string | Mensaje de error si la operación falló |
### `memory_delete`
Elimina memorias por conversationId.
Eliminar memorias por conversationId, blockId, blockName o una combinación. Admite eliminación masiva.
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `conversationId` | string | No | Identificador de conversación (ej., user-123, session-abc). Elimina todas las memorias de esta conversación. |
| `id` | string | No | Parámetro heredado para identificador de conversación. Usa conversationId en su lugar. Proporcionado para compatibilidad con versiones anteriores. |
| `conversationId` | string | No | Identificador de conversación (p. ej., user-123, session-abc). Si se proporciona solo, elimina todas las memorias para esta conversación en todos los bloques. |
| `id` | string | No | Parámetro heredado para el identificador de conversación. Use conversationId en su lugar. Proporcionado para compatibilidad con versiones anteriores. |
| `blockId` | string | No | Identificador de bloque. Si se proporciona solo, elimina todas las memorias para este bloque en todas las conversaciones. Si se proporciona con conversationId, elimina las memorias para esa conversación específica en este bloque. |
| `blockName` | string | No | Nombre del bloque. Alternativa a blockId. Si se proporciona solo, elimina todas las memorias para bloques con este nombre. Si se proporciona con conversationId, elimina las memorias para esa conversación en bloques con este nombre. |
#### Salida
| Parámetro | Tipo | Descripción |
| --------- | ---- | ----------- |
| `success` | boolean | Si la memoria se eliminó exitosamente |
| `success` | boolean | Si la memoria fue eliminada con éxito |
| `message` | string | Mensaje de éxito o error |
| `error` | string | Mensaje de error si la operación falló |

View File

@@ -111,8 +111,8 @@ Lee los últimos mensajes de los canales de Slack. Recupera el historial de conv
| `authMethod` | string | No | Método de autenticación: oauth o bot_token |
| `botToken` | string | No | Token del bot para Bot personalizado |
| `channel` | string | No | Canal de Slack del que leer mensajes (p. ej., #general) |
| `userId` | string | No | ID de usuario para conversación de mensaje directo (p. ej., U1234567890) |
| `limit` | number | No | Número de mensajes a recuperar (predeterminado: 10, máx: 15) |
| `userId` | string | No | ID de usuario para conversación por MD (p. ej., U1234567890) |
| `limit` | number | No | Número de mensajes a recuperar (predeterminado: 10, máx: 100) |
| `oldest` | string | No | Inicio del rango de tiempo (marca de tiempo) |
| `latest` | string | No | Fin del rango de tiempo (marca de tiempo) |

View File

@@ -47,11 +47,10 @@ Consultar datos de una tabla de Supabase
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | Nombre de la tabla Supabase a consultar |
| `schema` | string | No | Esquema de base de datos desde donde consultar \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| `filter` | string | No | Filtro PostgREST \(p. ej., "id=eq.123"\) |
| `orderBy` | string | No | Columna para ordenar \(añade DESC para descendente\) |
| `projectId` | string | Sí | Tu ID de proyecto de Supabase \(ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | El nombre de la tabla de Supabase a consultar |
| `filter` | string | No | Filtro de PostgREST \(ej., "id=eq.123"\) |
| `orderBy` | string | No | Columna para ordenar \(añade DESC para orden descendente\) |
| `limit` | number | No | Número máximo de filas a devolver |
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
@@ -69,10 +68,9 @@ Insertar datos en una tabla de Supabase
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | Nombre de la tabla Supabase donde insertar datos |
| `schema` | string | No | Esquema de base de datos donde insertar \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| `data` | array | Sí | Los datos a insertar \(array de objetos o un solo objeto\) |
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
@@ -90,10 +88,9 @@ Obtener una sola fila de una tabla de Supabase basada en criterios de filtro
#### Entrada
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | Nombre de la tabla Supabase a consultar |
| `schema` | string | No | Esquema de base de datos desde donde consultar \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto de Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | Nombre de la tabla de Supabase para consultar |
| `filter` | string | Sí | Filtro PostgREST para encontrar la fila específica \(p. ej., "id=eq.123"\) |
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
@@ -114,9 +111,8 @@ Actualizar filas en una tabla de Supabase según criterios de filtro
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | Nombre de la tabla Supabase a actualizar |
| `schema` | string | No | Esquema de base de datos donde actualizar \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| `filter` | string | Sí | Filtro PostgREST para identificar las filas a actualizar \(p. ej., "id=eq.123"\) |
| `data` | object | Sí | Datos a actualizar en las filas coincidentes |
| `data` | object | Sí | Datos para actualizar en las filas coincidentes |
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
#### Salida
@@ -136,7 +132,6 @@ Eliminar filas de una tabla de Supabase según criterios de filtro
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | Nombre de la tabla Supabase de la que eliminar |
| `schema` | string | No | Esquema de base de datos del que eliminar \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| `filter` | string | Sí | Filtro PostgREST para identificar las filas a eliminar \(p. ej., "id=eq.123"\) |
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
@@ -157,8 +152,7 @@ Insertar o actualizar datos en una tabla de Supabase (operación upsert)
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | Nombre de la tabla Supabase donde hacer upsert de datos |
| `schema` | string | No | Esquema de base de datos donde hacer upsert \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| `data` | array | Sí | Los datos para hacer upsert \(insertar o actualizar\) - array de objetos o un solo objeto |
| `data` | array | | Los datos para upsert \(insertar o actualizar\) - array de objetos o un solo objeto |
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
#### Salida
@@ -177,8 +171,7 @@ Contar filas en una tabla de Supabase
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | El nombre de la tabla Supabase de la que contar filas |
| `schema` | string | No | Esquema de base de datos desde el que contar \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| `table` | string | Sí | Nombre de la tabla Supabase de la que contar filas |
| `filter` | string | No | Filtro PostgREST \(p. ej., "status=eq.active"\) |
| `countType` | string | No | Tipo de conteo: exact, planned o estimated \(predeterminado: exact\) |
| `apiKey` | string | Sí | Tu clave secreta de rol de servicio de Supabase |
@@ -199,8 +192,7 @@ Realizar búsqueda de texto completo en una tabla de Supabase
| Parámetro | Tipo | Obligatorio | Descripción |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | Sí | ID de tu proyecto Supabase \(p. ej., jdrkgepadsdopsntdlom\) |
| `table` | string | Sí | El nombre de la tabla Supabase donde buscar |
| `schema` | string | No | Esquema de base de datos en el que buscar \(predeterminado: public\). Usa esto para acceder a tablas en otros esquemas. |
| `table` | string | Sí | Nombre de la tabla Supabase en la que buscar |
| `column` | string | Sí | La columna en la que buscar |
| `query` | string | Sí | La consulta de búsqueda |
| `searchType` | string | No | Tipo de búsqueda: plain, phrase o websearch \(predeterminado: websearch\) |

View File

@@ -47,42 +47,42 @@ La répartition des modèles montre :
## Options de tarification
<Tabs items={['Modèles hébergés', 'Apportez votre propre clé API']}>
<Tabs items={['Hosted Models', 'Bring Your Own API Key']}>
<Tab>
**Modèles hébergés** - Sim fournit des clés API avec un multiplicateur de prix de 2x :
**Modèles hébergés** - Sim fournit des clés API avec un multiplicateur de prix de 2,5x :
**OpenAI**
| Modèle | Prix de base (entrée/sortie) | Prix hébergé (entrée/sortie) |
| Modèle | Prix de base (Entrée/Sortie) | Prix hébergé (Entrée/Sortie) |
|-------|---------------------------|----------------------------|
| GPT-5.1 | 1,25 $ / 10,00 $ | 2,50 $ / 20,00 $ |
| GPT-5 | 1,25 $ / 10,00 $ | 2,50 $ / 20,00 $ |
| GPT-5 Mini | 0,25 $ / 2,00 $ | 0,50 $ / 4,00 $ |
| GPT-5 Nano | 0,05 $ / 0,40 $ | 0,10 $ / 0,80 $ |
| GPT-4o | 2,50 $ / 10,00 $ | 5,00 $ / 20,00 $ |
| GPT-4.1 | 2,00 $ / 8,00 $ | 4,00 $ / 16,00 $ |
| GPT-4.1 Mini | 0,40 $ / 1,60 $ | 0,80 $ / 3,20 $ |
| GPT-4.1 Nano | 0,10 $ / 0,40 $ | 0,20 $ / 0,80 $ |
| o1 | 15,00 $ / 60,00 $ | 30,00 $ / 120,00 $ |
| o3 | 2,00 $ / 8,00 $ | 4,00 $ / 16,00 $ |
| o4 Mini | 1,10 $ / 4,40 $ | 2,20 $ / 8,80 $ |
| GPT-5.1 | 1,25 $ / 10,00 $ | 3,13 $ / 25,00 $ |
| GPT-5 | 1,25 $ / 10,00 $ | 3,13 $ / 25,00 $ |
| GPT-5 Mini | 0,25 $ / 2,00 $ | 0,63 $ / 5,00 $ |
| GPT-5 Nano | 0,05 $ / 0,40 $ | 0,13 $ / 1,00 $ |
| GPT-4o | 2,50 $ / 10,00 $ | 6,25 $ / 25,00 $ |
| GPT-4.1 | 2,00 $ / 8,00 $ | 5,00 $ / 20,00 $ |
| GPT-4.1 Mini | 0,40 $ / 1,60 $ | 1,00 $ / 4,00 $ |
| GPT-4.1 Nano | 0,10 $ / 0,40 $ | 0,25 $ / 1,00 $ |
| o1 | 15,00 $ / 60,00 $ | 37,50 $ / 150,00 $ |
| o3 | 2,00 $ / 8,00 $ | 5,00 $ / 20,00 $ |
| o4 Mini | 1,10 $ / 4,40 $ | 2,75 $ / 11,00 $ |
**Anthropic**
| Modèle | Prix de base (entrée/sortie) | Prix hébergé (entrée/sortie) |
| Modèle | Prix de base (Entrée/Sortie) | Prix hébergé (Entrée/Sortie) |
|-------|---------------------------|----------------------------|
| Claude Opus 4.5 | 5,00 $ / 25,00 $ | 10,00 $ / 50,00 $ |
| Claude Opus 4.1 | 15,00 $ / 75,00 $ | 30,00 $ / 150,00 $ |
| Claude Sonnet 4.5 | 3,00 $ / 15,00 $ | 6,00 $ / 30,00 $ |
| Claude Sonnet 4.0 | 3,00 $ / 15,00 $ | 6,00 $ / 30,00 $ |
| Claude Haiku 4.5 | 1,00 $ / 5,00 $ | 2,00 $ / 10,00 $ |
| Claude Opus 4.5 | 5,00 $ / 25,00 $ | 12,50 $ / 62,50 $ |
| Claude Opus 4.1 | 15,00 $ / 75,00 $ | 37,50 $ / 187,50 $ |
| Claude Sonnet 4.5 | 3,00 $ / 15,00 $ | 7,50 $ / 37,50 $ |
| Claude Sonnet 4.0 | 3,00 $ / 15,00 $ | 7,50 $ / 37,50 $ |
| Claude Haiku 4.5 | 1,00 $ / 5,00 $ | 2,50 $ / 12,50 $ |
**Google**
| Modèle | Prix de base (entrée/sortie) | Prix hébergé (entrée/sortie) |
| Modèle | Prix de base (Entrée/Sortie) | Prix hébergé (Entrée/Sortie) |
|-------|---------------------------|----------------------------|
| Gemini 3 Pro Preview | 2,00 $ / 12,00 $ | 4,00 $ / 24,00 $ |
| Gemini 2.5 Pro | 1,25 $ / 10,00 $ | 2,50 $ / 20,00 $ |
| Gemini 2.5 Flash | 0,30 $ / 2,50 $ | 0,60 $ / 5,00 $ |
| Gemini 3 Pro Preview | 2,00 $ / 12,00 $ | 5,00 $ / 30,00 $ |
| Gemini 2.5 Pro | 0,15 $ / 0,60 $ | 0,38 $ / 1,50 $ |
| Gemini 2.5 Flash | 0,15 $ / 0,60 $ | 0,38 $ / 1,50 $ |
*Le multiplicateur 2x couvre les coûts d'infrastructure et de gestion des API.*
*Le multiplicateur de 2,5x couvre les coûts d'infrastructure et de gestion des API.*
</Tab>
<Tab>
@@ -187,7 +187,7 @@ Les différents forfaits d'abonnement ont des limites d'utilisation différentes
| Forfait | Limite d'utilisation mensuelle | Limites de débit (par minute) |
|------|-------------------|-------------------------|
| **Gratuit** | 20 $ | 5 sync, 10 async |
| **Gratuit** | 10 $ | 5 sync, 10 async |
| **Pro** | 100 $ | 10 sync, 50 async |
| **Équipe** | 500 $ (mutualisé) | 50 sync, 100 async |
| **Entreprise** | Personnalisé | Personnalisé |

View File

@@ -35,59 +35,53 @@ Une fois vos documents traités, vous pouvez visualiser et modifier les segments
<Image src="/static/knowledgebase/knowledgebase.png" alt="Vue des segments de document montrant le contenu traité" width={800} height={500} />
### Configuration des fragments
Lors de la création d'une base de connaissances, vous pouvez configurer la façon dont les documents sont divisés en fragments :
| Paramètre | Unité | Par défaut | Plage | Description |
|---------|------|---------|-------|-------------|
| **Taille max. du fragment** | jetons | 1 024 | 100-4 000 | Taille maximale de chaque fragment (1 jeton ≈ 4 caractères) |
| **Taille min. du fragment** | caractères | 1 | 1-2 000 | Taille minimale du fragment pour éviter les fragments minuscules |
| **Chevauchement** | caractères | 200 | 0-500 | Chevauchement de contexte entre fragments consécutifs |
- **Division hiérarchique** : respecte la structure du document (sections, paragraphes, phrases)
- **Taille par défaut des fragments** : 1 024 caractères
- **Plage configurable** : 100 à 4 000 caractères par fragment
- **Chevauchement intelligent** : 200 caractères par défaut pour préserver le contexte
- **Découpage hiérarchique** : respecte la structure du document (sections, paragraphes, phrases)
### Capacités d'édition
- **Modifier le contenu du fragment** : modifiez le contenu textuel des fragments individuels
- **Ajuster les limites du fragment** : fusionnez ou divisez les fragments selon les besoins
- **Ajouter des métadonnées** : enrichissez les fragments avec du contexte supplémentaire
- **Opérations en masse** : gérez plusieurs fragments efficacement
- **Modifier le contenu des fragments** : modifier le contenu textuel des fragments individuels
- **Ajuster les limites des fragments** : fusionner ou diviser les fragments selon les besoins
- **Ajouter des métadonnées** : enrichir les fragments avec du contexte supplémentaire
- **Opérations en masse** : gérer efficacement plusieurs fragments
## Traitement avancé des PDF
Pour les documents PDF, Sim offre des capacités de traitement améliorées :
### Prise en charge de l'OCR
Lorsqu'il est configuré avec Azure ou [Mistral OCR](https://docs.mistral.ai/ocr/) :
- **Traitement de documents numérisés** : extrayez le texte des PDF basés sur des images
- **Gestion de contenu mixte** : traitez les PDF contenant à la fois du texte et des images
- **Haute précision** : les modèles d'IA avancés garantissent une extraction de texte précise
### Support OCR
Lorsque configuré avec Azure ou [Mistral OCR](https://docs.mistral.ai/ocr/) :
- **Traitement de documents numérisés** : extraction de texte à partir de PDF basés sur des images
- **Gestion de contenu mixte** : traitement des PDF contenant à la fois du texte et des images
- **Haute précision** : les modèles d'IA avancés assurent une extraction précise du texte
## Utilisation du bloc de connaissances dans les workflows
## Utilisation du bloc de connaissances dans les flux de travail
Une fois vos documents traités, vous pouvez les utiliser dans vos workflows d'IA via le bloc de connaissances. Cela active la génération augmentée par récupération (RAG), permettant à vos agents d'IA d'accéder à votre contenu documentaire et de raisonner dessus pour fournir des réponses plus précises et contextuelles.
Une fois vos documents traités, vous pouvez les utiliser dans vos flux de travail d'IA grâce au bloc de connaissances. Cela permet la génération augmentée par récupération (RAG), permettant à vos agents IA d'accéder et de raisonner sur le contenu de vos documents pour fournir des réponses plus précises et contextuelles.
<Image src="/static/knowledgebase/knowledgebase-2.png" alt="Utilisation du bloc de connaissances dans les workflows" width={800} height={500} />
<Image src="/static/knowledgebase/knowledgebase-2.png" alt="Utilisation du bloc de connaissances dans les flux de travail" width={800} height={500} />
### Fonctionnalités du bloc de connaissances
- **Recherche sémantique** : trouvez du contenu pertinent à l'aide de requêtes en langage naturel
- **Intégration contextuelle** : incluez automatiquement les fragments pertinents dans les prompts de l'agent
- **Récupération dynamique** : la recherche s'effectue en temps réel pendant l'exécution du workflow
- **Score de pertinence** : résultats classés par similarité sémantique
- **Recherche sémantique** : trouver du contenu pertinent à l'aide de requêtes en langage naturel
- **Intégration du contexte** : inclure automatiquement les fragments pertinents dans les prompts des agents
- **Récupération dynamique** : la recherche s'effectue en temps réel pendant l'exécution du flux de travail
- **Évaluation de la pertinence** : résultats classés par similarité sémantique
### Options d'intégration
- **Prompts système** : fournissez du contexte à vos agents IA
- **Contexte dynamique** : recherchez et incluez des informations pertinentes pendant les conversations
- **Recherche multi-documents** : interrogez l'ensemble de votre base de connaissances
- **Recherche filtrée** : combinez avec des tags pour une récupération de contenu précise
- **Prompts système** : fournir du contexte à vos agents IA
- **Contexte dynamique** : rechercher et inclure des informations pertinentes pendant les conversations
- **Recherche multi-documents** : interroger l'ensemble de votre base de connaissances
- **Recherche filtrée** : combiner avec des tags pour une récupération précise du contenu
## Technologie de recherche vectorielle
Sim utilise la recherche vectorielle propulsée par [pgvector](https://github.com/pgvector/pgvector) pour comprendre le sens et le contexte de votre contenu :
Sim utilise la recherche vectorielle alimentée par [pgvector](https://github.com/pgvector/pgvector) pour comprendre le sens et le contexte de votre contenu :
### Compréhension sémantique
- **Recherche contextuelle** : trouve du contenu pertinent même lorsque les mots-clés exacts ne correspondent pas
- **Récupération basée sur les concepts** : comprend les relations entre les idées
- **Support multilingue** : fonctionne dans différentes langues
- **Prise en charge multilingue** : fonctionne dans différentes langues
- **Reconnaissance des synonymes** : trouve des termes et concepts associés
### Capacités de recherche
@@ -96,26 +90,26 @@ Sim utilise la recherche vectorielle propulsée par [pgvector](https://github.co
- **Recherche hybride** : combine la recherche vectorielle et la recherche traditionnelle par mots-clés
- **Résultats configurables** : contrôlez le nombre et le seuil de pertinence des résultats
## Gestion des documents
## Gestion documentaire
### Fonctionnalités d'organisation
- **Téléchargement en masse** : téléchargez plusieurs fichiers à la fois via l'API asynchrone
- **Statut de traitement** : mises à jour en temps réel sur le traitement des documents
- **État de traitement** : mises à jour en temps réel sur le traitement des documents
- **Recherche et filtrage** : trouvez rapidement des documents dans de grandes collections
- **Suivi des métadonnées** : capture automatique des informations de fichier et des détails de traitement
### Sécurité et confidentialité
- **Stockage sécurisé** : documents stockés avec une sécurité de niveau entreprise
- **Contrôle d'accès** : permissions basées sur l'espace de travail
- **Contrôle d'accès** : autorisations basées sur l'espace de travail
- **Isolation du traitement** : chaque espace de travail dispose d'un traitement de documents isolé
- **Conservation des données** : configurez les politiques de conservation des documents
## Premiers pas
1. **Accédez à votre base de connaissances** : accès depuis la barre latérale de votre espace de travail
1. **Accédez à votre base de connaissances** : accessible depuis la barre latérale de votre espace de travail
2. **Téléchargez des documents** : glissez-déposez ou sélectionnez des fichiers à télécharger
3. **Surveillez le traitement** : observez le traitement et la segmentation des documents
4. **Explorez les segments** : visualisez et modifiez le contenu traité
5. **Ajoutez aux workflows** : utilisez le bloc Knowledge pour intégrer avec vos agents IA
3. **Surveillez le traitement** : observez le traitement et le découpage des documents
4. **Explorez les fragments** : visualisez et modifiez le contenu traité
5. **Ajoutez aux flux de travail** : utilisez le bloc Connaissances pour l'intégrer à vos agents IA
La base de connaissances transforme vos documents statiques en une ressource intelligente et consultable que vos workflows IA peuvent exploiter pour des réponses plus éclairées et contextuelles.
La base de connaissances transforme vos documents statiques en une ressource intelligente et consultable que vos flux de travail IA peuvent exploiter pour des réponses plus informées et contextuelles.

View File

@@ -38,19 +38,17 @@ Créer un nouveau contact dans Intercom avec email, external_id ou role
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ------------ | ----------- |
| `role` | string | Non | Le rôle du contact. Accepte « user » ou « lead ». Par défaut « lead » si non spécifié. |
| `email` | string | Non | L'adresse e-mail du contact |
| --------- | ---- | ----------- | ----------- |
| `email` | string | Non | L'adresse email du contact |
| `external_id` | string | Non | Un identifiant unique pour le contact fourni par le client |
| `phone` | string | Non | Le numéro de téléphone du contact |
| `name` | string | Non | Le nom du contact |
| `avatar` | string | Non | Une URL d'image d'avatar pour le contact |
| `signed_up_at` | number | Non | L'heure d'inscription de l'utilisateur sous forme d'horodatage Unix |
| `last_seen_at` | number | Non | L'heure de dernière activité de l'utilisateur sous forme d'horodatage Unix |
| `owner_id` | string | Non | L'identifiant d'un administrateur auquel la propriété du compte du contact a été attribuée |
| `unsubscribed_from_emails` | boolean | Non | Indique si le contact s'est désabonné des e-mails |
| `custom_attributes` | string | Non | Attributs personnalisés sous forme d'objet JSON (par ex., \{"nom_attribut": "valeur"\}) |
| `company_id` | string | Non | Identifiant de l'entreprise à associer au contact lors de la création |
| `signed_up_at` | number | Non | L'heure à laquelle l'utilisateur s'est inscrit sous forme d'horodatage Unix |
| `last_seen_at` | number | Non | L'heure à laquelle l'utilisateur a été vu pour la dernière fois sous forme d'horodatage Unix |
| `owner_id` | string | Non | L'identifiant d'un administrateur qui a été assigné comme propriétaire du compte du contact |
| `unsubscribed_from_emails` | boolean | Non | Indique si le contact est désabonné des emails |
| `custom_attributes` | string | Non | Attributs personnalisés sous forme d'objet JSON (par exemple, \{"nom_attribut": "valeur"\}) |
#### Sortie
@@ -83,20 +81,17 @@ Mettre à jour un contact existant dans Intercom
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ------------ | ----------- |
| `contactId` | string | Oui | Identifiant du contact à mettre à jour |
| `role` | string | Non | Le rôle du contact. Accepte « user » ou « lead ». |
| `external_id` | string | Non | Un identifiant unique pour le contact fourni par le client |
| `email` | string | Non | L'adresse e-mail du contact |
| `phone` | string | Non | Le numéro de téléphone du contact |
| `name` | string | Non | Le nom du contact |
| `avatar` | string | Non | Une URL d'image d'avatar pour le contact |
| `signed_up_at` | number | Non | L'heure d'inscription de l'utilisateur sous forme d'horodatage Unix |
| `last_seen_at` | number | Non | L'heure de dernière activité de l'utilisateur sous forme d'horodatage Unix |
| `owner_id` | string | Non | L'identifiant d'un administrateur auquel la propriété du compte du contact a été attribuée |
| `unsubscribed_from_emails` | boolean | Non | Indique si le contact s'est désabonné des e-mails |
| `custom_attributes` | string | Non | Attributs personnalisés sous forme d'objet JSON (par ex., \{"nom_attribut": "valeur"\}) |
| `company_id` | string | Non | Identifiant de l'entreprise à associer au contact |
| --------- | ---- | ----------- | ----------- |
| `contactId` | string | Oui | ID du contact à mettre à jour |
| `email` | string | Non | Adresse e-mail du contact |
| `phone` | string | Non | Numéro de téléphone du contact |
| `name` | string | Non | Nom du contact |
| `avatar` | string | Non | URL de l'image d'avatar pour le contact |
| `signed_up_at` | number | Non | Moment où l'utilisateur s'est inscrit, en timestamp Unix |
| `last_seen_at` | number | Non | Moment où l'utilisateur a été vu pour la dernière fois, en timestamp Unix |
| `owner_id` | string | Non | ID d'un administrateur qui a été assigné comme propriétaire du compte du contact |
| `unsubscribed_from_emails` | boolean | Non | Indique si le contact est désabonné des e-mails |
| `custom_attributes` | string | Non | Attributs personnalisés sous forme d'objet JSON (par exemple, \{"nom_attribut": "valeur"\}) |
#### Sortie
@@ -130,12 +125,10 @@ Rechercher des contacts dans Intercom à l'aide d'une requête
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `query` | string | Oui | Requête de recherche \(par ex., \{"field":"email","operator":"=","value":"user@example.com"\}\) |
| `per_page` | number | Non | Nombre de résultats par page \(max : 150\) |
| `starting_after` | string | Non | Curseur pour la pagination |
| `sort_field` | string | Non | Champ de tri \(par ex., "name", "created_at", "last_seen_at"\) |
| `sort_order` | string | Non | Ordre de tri : "ascending" ou "descending" |
| --------- | ---- | -------- | ----------- |
| `query` | chaîne | Oui | Requête de recherche \(ex., \{"field":"email","operator":"=","value":"user@example.com"\}\) |
| `per_page` | nombre | Non | Nombre de résultats par page \(max : 150\) |
| `starting_after` | chaîne | Non | Curseur pour la pagination |
#### Sortie
@@ -168,16 +161,15 @@ Créer ou mettre à jour une entreprise dans Intercom
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| --------- | ---- | ---------- | ----------- |
| `company_id` | string | Oui | Votre identifiant unique pour l'entreprise |
| `name` | string | Non | Le nom de l'entreprise |
| `website` | string | Non | Le site web de l'entreprise |
| `plan` | string | Non | Le nom du plan de l'entreprise |
| `plan` | string | Non | Le nom du forfait de l'entreprise |
| `size` | number | Non | Le nombre d'employés dans l'entreprise |
| `industry` | string | Non | Le secteur d'activité de l'entreprise |
| `monthly_spend` | number | Non | Le chiffre d'affaires que l'entreprise génère pour votre activité. Remarque : ce champ tronque les nombres décimaux en entiers \(par ex., 155,98 devient 155\) |
| `monthly_spend` | number | Non | Le montant des revenus que l'entreprise génère pour votre activité. Remarque : ce champ tronque les décimales en nombres entiers (par exemple, 155,98 devient 155) |
| `custom_attributes` | string | Non | Attributs personnalisés sous forme d'objet JSON |
| `remote_created_at` | number | Non | La date de création de l'entreprise par vous sous forme d'horodatage Unix |
#### Sortie
@@ -213,7 +205,6 @@ Liste toutes les entreprises d'Intercom avec prise en charge de la pagination. R
| --------- | ---- | ----------- | ----------- |
| `per_page` | number | Non | Nombre de résultats par page |
| `page` | number | Non | Numéro de page |
| `starting_after` | string | Non | Curseur pour la pagination \(préféré à la pagination par page\) |
#### Sortie
@@ -229,10 +220,9 @@ Récupérer une seule conversation par ID depuis Intercom
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| --------- | ---- | ----------- | ----------- |
| `conversationId` | string | Oui | ID de la conversation à récupérer |
| `display_as` | string | Non | Définir sur "plaintext" pour récupérer les messages en texte brut |
| `include_translations` | boolean | Non | Lorsque true, les parties de la conversation seront traduites dans la langue détectée de la conversation |
| `display_as` | string | Non | Définir à "plaintext" pour récupérer les messages en texte brut |
#### Sortie
@@ -248,11 +238,9 @@ Lister toutes les conversations depuis Intercom avec prise en charge de la pagin
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| --------- | ---- | ----------- | ----------- |
| `per_page` | number | Non | Nombre de résultats par page \(max : 150\) |
| `starting_after` | string | Non | Curseur pour la pagination |
| `sort` | string | Non | Champ de tri \(ex., "waiting_since", "updated_at", "created_at"\) |
| `order` | string | Non | Ordre de tri : "asc" \(croissant\) ou "desc" \(décroissant\) |
#### Sortie
@@ -268,13 +256,12 @@ Répondre à une conversation en tant qu'administrateur dans Intercom
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | -------- | ----------- |
| --------- | ---- | ---------- | ----------- |
| `conversationId` | string | Oui | ID de la conversation à laquelle répondre |
| `message_type` | string | Oui | Type de message : "comment" ou "note" |
| `body` | string | Oui | Corps du texte de la réponse |
| `admin_id` | string | Non | ID de l'administrateur qui rédige la réponse. Si non fourni, un administrateur par défaut \(Operator/Fin\) sera utilisé. |
| `attachment_urls` | string | Non | Liste d'URL d'images séparées par des virgules \(max 10\) |
| `created_at` | number | Non | Horodatage Unix du moment où la réponse a été créée. Si non fourni, l'heure actuelle est utilisée. |
| `body` | string | Oui | Le corps du texte de la réponse |
| `admin_id` | string | Non | L'ID de l'administrateur qui rédige la réponse. Si non fourni, un administrateur par défaut (Operator/Fin) sera utilisé. |
| `attachment_urls` | string | Non | Liste d'URLs d'images séparées par des virgules (max 10) |
#### Sortie
@@ -290,12 +277,10 @@ Rechercher des conversations dans Intercom à l'aide d'une requête
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| --------- | ---- | -------- | ----------- |
| `query` | string | Oui | Requête de recherche sous forme d'objet JSON |
| `per_page` | number | Non | Nombre de résultats par page \(max : 150\) |
| `per_page` | number | Non | Nombre de résultats par page (max : 150) |
| `starting_after` | string | Non | Curseur pour la pagination |
| `sort_field` | string | Non | Champ de tri \(par exemple, "created_at", "updated_at"\) |
| `sort_order` | string | Non | Ordre de tri : "ascending" ou "descending" |
#### Sortie
@@ -311,14 +296,10 @@ Créer un nouveau ticket dans Intercom
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| --------- | ---- | -------- | ----------- |
| `ticket_type_id` | string | Oui | L'ID du type de ticket |
| `contacts` | string | Oui | Tableau JSON d'identifiants de contacts \(par exemple, \[\{"id": "contact_id"\}\]\) |
| `contacts` | string | Oui | Tableau JSON d'identifiants de contact (par ex., \[\{"id": "contact_id"\}\]) |
| `ticket_attributes` | string | Oui | Objet JSON avec les attributs du ticket incluant _default_title_ et _default_description_ |
| `company_id` | string | Non | ID de l'entreprise à associer au ticket |
| `created_at` | number | Non | Horodatage Unix du moment où le ticket a été créé. Si non fourni, l'heure actuelle est utilisée. |
| `conversation_to_link_id` | string | Non | ID d'une conversation existante à lier à ce ticket |
| `disable_notifications` | boolean | Non | Lorsque défini sur true, supprime les notifications lors de la création du ticket |
#### Sortie
@@ -351,16 +332,14 @@ Créer et envoyer un nouveau message initié par l'administrateur dans Intercom
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `message_type` | string | Oui | Type de message : "inapp" pour les messages in-app ou "email" pour les messages e-mail |
| `template` | string | Oui | Style du modèle de message : "plain" pour texte brut ou "personal" pour style personnalisé |
| `subject` | string | Non | Le sujet du message \(pour le type e-mail\) |
| `body` | string | Oui | Le corps du message |
| --------- | ---- | -------- | ----------- |
| `message_type` | string | Oui | Type de message : "inapp" ou "email" |
| `subject` | string | Non | Objet du message (pour le type email) |
| `body` | string | Oui | Corps du message |
| `from_type` | string | Oui | Type d'expéditeur : "admin" |
| `from_id` | string | Oui | L'ID de l'administrateur qui envoie le message |
| `from_id` | string | Oui | ID de l'administrateur qui envoie le message |
| `to_type` | string | Oui | Type de destinataire : "contact" |
| `to_id` | string | Oui | L'ID du contact qui reçoit le message |
| `created_at` | number | Non | Horodatage Unix du moment où le message a été créé. Si non fourni, l'heure actuelle est utilisée. |
| `to_id` | string | Oui | ID du contact qui reçoit le message |
#### Sortie

View File

@@ -89,21 +89,15 @@ Rédiger une demande Jira
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `domain` | chaîne | Oui | Votre domaine Jira \(ex. : votreentreprise.atlassian.net\) |
| `projectId` | chaîne | Oui | ID du projet pour le ticket |
| `summary` | chaîne | Oui | Résumé du ticket |
| `description` | chaîne | Non | Description du ticket |
| `priority` | chaîne | Non | ID ou nom de la priorité du ticket \(ex. : "10000" ou "Haute"\) |
| `assignee` | chaîne | Non | ID de compte de l'assigné pour le ticket |
| `cloudId` | chaîne | Non | ID Cloud Jira pour l'instance. S'il n'est pas fourni, il sera récupéré à l'aide du domaine. |
| `issueType` | chaîne | Oui | Type de ticket à créer \(ex. : tâche, story\) |
| `labels` | tableau | Non | Étiquettes pour le ticket \(tableau de noms d'étiquettes\) |
| `duedate` | chaîne | Non | Date d'échéance du ticket \(format : AAAA-MM-JJ\) |
| `reporter` | chaîne | Non | ID de compte du rapporteur pour le ticket |
| `environment` | chaîne | Non | Informations d'environnement pour le ticket |
| `customFieldId` | chaîne | Non | ID du champ personnalisé \(ex. : customfield_10001\) |
| `customFieldValue` | chaîne | Non | Valeur pour le champ personnalisé |
| --------- | ---- | -------- | ----------- |
| `domain` | string | Oui | Votre domaine Jira (ex. : votreentreprise.atlassian.net) |
| `projectId` | string | Oui | ID du projet pour la demande |
| `summary` | string | Oui | Résumé de la demande |
| `description` | string | Non | Description de la demande |
| `priority` | string | Non | Priorité de la demande |
| `assignee` | string | Non | Assigné de la demande |
| `cloudId` | string | Non | ID Jira Cloud pour l'instance. S'il n'est pas fourni, il sera récupéré à l'aide du domaine. |
| `issueType` | string | Oui | Type de demande à créer (ex. : Tâche, Story) |
#### Sortie
@@ -113,7 +107,6 @@ Rédiger une demande Jira
| `issueKey` | chaîne | Clé du ticket créé \(ex. : PROJ-123\) |
| `summary` | chaîne | Résumé du ticket |
| `url` | chaîne | URL vers le ticket créé |
| `assigneeId` | chaîne | ID de compte de l'utilisateur assigné \(si assigné\) |
### `jira_bulk_read`
@@ -527,31 +520,7 @@ Supprimer un observateur d'un ticket Jira
| `issueKey` | string | Clé du ticket |
| `watcherAccountId` | string | ID du compte observateur supprimé |
### `jira_get_users`
Récupère les utilisateurs Jira. Si un ID de compte est fourni, renvoie un seul utilisateur. Sinon, renvoie une liste de tous les utilisateurs.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `domain` | chaîne | Oui | Votre domaine Jira \(ex. : votreentreprise.atlassian.net\) |
| `accountId` | chaîne | Non | ID de compte optionnel pour obtenir un utilisateur spécifique. S'il n'est pas fourni, renvoie tous les utilisateurs. |
| `startAt` | nombre | Non | L'index du premier utilisateur à renvoyer \(pour la pagination, par défaut : 0\) |
| `maxResults` | nombre | Non | Nombre maximum d'utilisateurs à renvoyer \(par défaut : 50\) |
| `cloudId` | chaîne | Non | ID Cloud Jira pour l'instance. S'il n'est pas fourni, il sera récupéré à l'aide du domaine. |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `ts` | chaîne | Horodatage de l'opération |
| `users` | json | Tableau d'utilisateurs avec accountId, displayName, emailAddress, statut actif et avatarUrls |
| `total` | nombre | Nombre total d'utilisateurs renvoyés |
| `startAt` | nombre | Index de début de pagination |
| `maxResults` | nombre | Nombre maximum de résultats par page |
## Remarques
## Notes
- Catégorie : `tools`
- Type : `jira`

View File

@@ -1,6 +1,6 @@
---
title: Mémoire
description: Ajouter un magasin de mémoire
description: Ajouter un stockage de mémoire
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -12,50 +12,53 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
## Instructions d'utilisation
Intégrer la mémoire dans le flux de travail. Permet d'ajouter, d'obtenir une mémoire, d'obtenir toutes les mémoires et de supprimer des mémoires.
Intégrer la mémoire dans le flux de travail. Peut ajouter, obtenir une mémoire, obtenir toutes les mémoires et supprimer des mémoires.
## Outils
### `memory_add`
Ajouter une nouvelle mémoire à la base de données ou ajouter à une mémoire existante avec le même ID.
Ajoutez une nouvelle mémoire à la base de données ou complétez une mémoire existante avec le même ID.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `conversationId` | chaîne | Non | Identifiant de conversation \(par ex., user-123, session-abc\). Si une mémoire avec cet identifiant existe déjà, le nouveau message y sera ajouté. |
| --------- | ---- | ---------- | ----------- |
| `conversationId` | chaîne | Non | Identifiant de conversation (par ex., user-123, session-abc). Si une mémoire avec cet identifiant de conversation existe déjà pour ce bloc, le nouveau message y sera ajouté. |
| `id` | chaîne | Non | Paramètre hérité pour l'identifiant de conversation. Utilisez conversationId à la place. Fourni pour la rétrocompatibilité. |
| `role` | chaîne | Oui | Rôle pour la mémoire de l'agent \(user, assistant ou system\) |
| `role` | chaîne | Oui | Rôle pour la mémoire de l'agent (user, assistant, ou system) |
| `content` | chaîne | Oui | Contenu pour la mémoire de l'agent |
| `blockId` | chaîne | Non | ID de bloc optionnel. Si non fourni, utilise l'ID du bloc actuel du contexte d'exécution, ou par défaut "default". |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | booléen | Indique si la mémoire a été ajoutée avec succès |
| `memories` | tableau | Tableau d'objets mémoire incluant la mémoire nouvelle ou mise à jour |
| `error` | chaîne | Message d'erreur en cas d'échec de l'opération |
| `success` | boolean | Indique si la mémoire a été ajoutée avec succès |
| `memories` | array | Tableau d'objets de mémoire incluant la nouvelle mémoire ou celle mise à jour |
| `error` | string | Message d'erreur si l'opération a échoué |
### `memory_get`
Récupérer la mémoire par conversationId. Renvoie les mémoires correspondantes.
Récupérer la mémoire par conversationId, blockId, blockName, ou une combinaison. Renvoie toutes les mémoires correspondantes.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `conversationId` | chaîne | Non | Identifiant de conversation \(par ex., user-123, session-abc\). Renvoie les mémoires pour cette conversation. |
| --------- | ---- | ---------- | ----------- |
| `conversationId` | chaîne | Non | Identifiant de conversation (par ex., user-123, session-abc). Si fourni seul, renvoie toutes les mémoires pour cette conversation à travers tous les blocs. |
| `id` | chaîne | Non | Paramètre hérité pour l'identifiant de conversation. Utilisez conversationId à la place. Fourni pour la rétrocompatibilité. |
| `blockId` | chaîne | Non | Identifiant de bloc. Si fourni seul, renvoie toutes les mémoires pour ce bloc à travers toutes les conversations. Si fourni avec conversationId, renvoie les mémoires pour cette conversation spécifique dans ce bloc. |
| `blockName` | chaîne | Non | Nom du bloc. Alternative à blockId. Si fourni seul, renvoie toutes les mémoires pour les blocs avec ce nom. Si fourni avec conversationId, renvoie les mémoires pour cette conversation dans les blocs avec ce nom. |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | booléen | Indique si la mémoire a été récupérée avec succès |
| `memories` | tableau | Tableau d'objets mémoire avec les champs conversationId et data |
| `memories` | tableau | Tableau d'objets de mémoire avec les champs conversationId, blockId, blockName et data |
| `message` | chaîne | Message de succès ou d'erreur |
| `error` | chaîne | Message d'erreur en cas d'échec |
| `error` | chaîne | Message d'erreur si l'opération a échoué |
### `memory_get_all`
@@ -71,28 +74,30 @@ Récupérer toutes les mémoires de la base de données
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | booléen | Indique si toutes les mémoires ont été récupérées avec succès |
| `memories` | tableau | Tableau de tous les objets mémoire avec les champs key, conversationId et data |
| `memories` | tableau | Tableau de tous les objets de mémoire avec les champs key, conversationId, blockId, blockName et data |
| `message` | chaîne | Message de succès ou d'erreur |
| `error` | chaîne | Message d'erreur en cas d'échec |
| `error` | chaîne | Message d'erreur si l'opération a échoué |
### `memory_delete`
Supprimer les mémoires par conversationId.
Supprimer des mémoires par conversationId, blockId, blockName, ou une combinaison. Prend en charge la suppression en masse.
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `conversationId` | chaîne | Non | Identifiant de conversation (par ex., user-123, session-abc). Supprime toutes les mémoires pour cette conversation. |
| `conversationId` | chaîne | Non | Identifiant de conversation \(par exemple, user-123, session-abc\). Si fourni seul, supprime toutes les mémoires pour cette conversation dans tous les blocs. |
| `id` | chaîne | Non | Paramètre hérité pour l'identifiant de conversation. Utilisez conversationId à la place. Fourni pour la rétrocompatibilité. |
| `blockId` | chaîne | Non | Identifiant de bloc. Si fourni seul, supprime toutes les mémoires pour ce bloc dans toutes les conversations. Si fourni avec conversationId, supprime les mémoires pour cette conversation spécifique dans ce bloc. |
| `blockName` | chaîne | Non | Nom du bloc. Alternative à blockId. Si fourni seul, supprime toutes les mémoires pour les blocs avec ce nom. Si fourni avec conversationId, supprime les mémoires pour cette conversation dans les blocs avec ce nom. |
#### Sortie
| Paramètre | Type | Description |
| --------- | ---- | ----------- |
| `success` | booléen | Indique si la mémoire a été supprimée avec succès |
| `success` | booléen | Indique si le souvenir a été supprimé avec succès |
| `message` | chaîne | Message de succès ou d'erreur |
| `error` | chaîne | Message d'erreur en cas d'échec |
| `error` | chaîne | Message d'erreur si l'opération a échoué |
## Notes

View File

@@ -107,14 +107,14 @@ Lisez les derniers messages des canaux Slack. Récupérez l'historique des conve
#### Entrée
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| --------- | ---- | ---------- | ----------- |
| `authMethod` | chaîne | Non | Méthode d'authentification : oauth ou bot_token |
| `botToken` | chaîne | Non | Jeton du bot pour Bot personnalisé |
| `channel` | chaîne | Non | Canal Slack depuis lequel lire les messages \(ex. : #general\) |
| `userId` | chaîne | Non | ID utilisateur pour la conversation en message direct \(ex. : U1234567890\) |
| `limit` | nombre | Non | Nombre de messages à récupérer \(par défaut : 10, max : 15\) |
| `oldest` | chaîne | Non | Début de la plage horaire \(horodatage\) |
| `latest` | chaîne | Non | Fin de la plage horaire \(horodatage\) |
| `channel` | chaîne | Non | Canal Slack pour lire les messages \(ex. : #general\) |
| `userId` | chaîne | Non | ID utilisateur pour la conversation en MP \(ex. : U1234567890\) |
| `limit` | nombre | Non | Nombre de messages à récupérer \(par défaut : 10, max : 100\) |
| `oldest` | chaîne | Non | Début de la plage temporelle \(horodatage\) |
| `latest` | chaîne | Non | Fin de la plage temporelle \(horodatage\) |
#### Sortie

View File

@@ -47,13 +47,12 @@ Interroger des données d'une table Supabase
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `table` | string | Oui | Le nom de la table Supabase à interroger |
| `schema` | string | Non | Schéma de base de données à interroger \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `filter` | string | Non | Filtre PostgREST \(ex. : "id=eq.123"\) |
| `orderBy` | string | Non | Colonne pour le tri \(ajoutez DESC pour l'ordre décroissant\) |
| `limit` | number | Non | Nombre maximum de lignes à retourner |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
| `projectId` | chaîne | Oui | Votre ID de projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `table` | chaîne | Oui | Le nom de la table Supabase à interroger |
| `filter` | chaîne | Non | Filtre PostgREST \(ex. : "id=eq.123"\) |
| `orderBy` | chaîne | Non | Colonne pour le tri \(ajoutez DESC pour décroissant\) |
| `limit` | nombre | Non | Nombre maximum de lignes à retourner |
| `apiKey` | chaîne | Oui | Votre clé secrète de rôle de service Supabase |
#### Sortie
@@ -70,11 +69,10 @@ Insérer des données dans une table Supabase
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `table` | string | Oui | Le nom de la table Supabase dans laquelle insérer des données |
| `schema` | string | Non | Schéma de base de données dans lequel insérer \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `data` | array | Oui | Les données à insérer \(tableau d'objets ou un seul objet\) |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
| `projectId` | chaîne | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
| `table` | chaîne | Oui | Le nom de la table Supabase dans laquelle insérer des données |
| `data` | tableau | Oui | Les données à insérer (tableau d'objets ou un seul objet) |
| `apiKey` | chaîne | Oui | Votre clé secrète de rôle de service Supabase |
#### Sortie
@@ -91,10 +89,9 @@ Obtenir une seule ligne d'une table Supabase selon des critères de filtrage
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `projectId` | string | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
| `table` | string | Oui | Le nom de la table Supabase à interroger |
| `schema` | string | Non | Schéma de base de données à interroger \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `filter` | string | Oui | Filtre PostgREST pour trouver la ligne spécifique \(ex. : "id=eq.123"\) |
| `filter` | string | Oui | Filtre PostgREST pour trouver la ligne spécifique (ex. : "id=eq.123") |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
#### Sortie
@@ -112,10 +109,9 @@ Mettre à jour des lignes dans une table Supabase selon des critères de filtrag
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `projectId` | string | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
| `table` | string | Oui | Le nom de la table Supabase à mettre à jour |
| `schema` | string | Non | Schéma de base de données dans lequel mettre à jour \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `filter` | string | Oui | Filtre PostgREST pour identifier les lignes à mettre à jour \(ex. : "id=eq.123"\) |
| `filter` | string | Oui | Filtre PostgREST pour identifier les lignes à mettre à jour (ex. : "id=eq.123") |
| `data` | object | Oui | Données à mettre à jour dans les lignes correspondantes |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
@@ -134,10 +130,9 @@ Supprimer des lignes d'une table Supabase selon des critères de filtrage
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `table` | string | Oui | Le nom de la table Supabase à partir de laquelle supprimer |
| `schema` | string | Non | Schéma de base de données à partir duquel supprimer \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `filter` | string | Oui | Filtre PostgREST pour identifier les lignes à supprimer \(ex. : "id=eq.123"\) |
| `projectId` | string | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
| `table` | string | Oui | Le nom de la table Supabase d'où supprimer des lignes |
| `filter` | string | Oui | Filtre PostgREST pour identifier les lignes à supprimer (ex. : "id=eq.123") |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
#### Sortie
@@ -155,11 +150,10 @@ Insérer ou mettre à jour des données dans une table Supabase (opération upse
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `table` | string | Oui | Le nom de la table Supabase dans laquelle effectuer l'upsert |
| `schema` | string | Non | Schéma de base de données dans lequel effectuer l'upsert \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `data` | array | Oui | Les données à insérer ou mettre à jour \(upsert\) - tableau d'objets ou un seul objet |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
| `projectId` | chaîne | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
| `table` | chaîne | Oui | Le nom de la table Supabase dans laquelle upserter des données |
| `data` | tableau | Oui | Les données à upserter (insérer ou mettre à jour) - tableau d'objets ou un seul objet |
| `apiKey` | chaîne | Oui | Votre clé secrète de rôle de service Supabase |
#### Sortie
@@ -176,12 +170,11 @@ Compter les lignes dans une table Supabase
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `table` | string | Oui | Le nom de la table Supabase dont compter les lignes |
| `schema` | string | Non | Schéma de base de données à partir duquel compter \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `filter` | string | Non | Filtre PostgREST \(ex. : "status=eq.active"\) |
| `countType` | string | Non | Type de comptage : exact, planned ou estimated \(par défaut : exact\) |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
| `projectId` | chaîne | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
| `table` | chaîne | Oui | Le nom de la table Supabase dont compter les lignes |
| `filter` | chaîne | Non | Filtre PostgREST (ex. : "status=eq.active") |
| `countType` | chaîne | Non | Type de comptage : exact, planned ou estimated (par défaut : exact) |
| `apiKey` | chaîne | Oui | Votre clé secrète de rôle de service Supabase |
#### Sortie
@@ -198,15 +191,14 @@ Effectuer une recherche en texte intégral sur une table Supabase
| Paramètre | Type | Obligatoire | Description |
| --------- | ---- | ----------- | ----------- |
| `projectId` | string | Oui | L'ID de votre projet Supabase \(ex. : jdrkgepadsdopsntdlom\) |
| `table` | string | Oui | Le nom de la table Supabase à rechercher |
| `schema` | string | Non | Schéma de base de données dans lequel rechercher \(par défaut : public\). Utilisez ceci pour accéder aux tables dans d'autres schémas. |
| `column` | string | Oui | La colonne dans laquelle rechercher |
| `query` | string | Oui | La requête de recherche |
| `searchType` | string | Non | Type de recherche : plain, phrase ou websearch \(par défaut : websearch\) |
| `language` | string | Non | Langue pour la configuration de recherche textuelle \(par défaut : english\) |
| `limit` | number | Non | Nombre maximum de lignes à retourner |
| `apiKey` | string | Oui | Votre clé secrète de rôle de service Supabase |
| `projectId` | chaîne | Oui | L'ID de votre projet Supabase (ex. : jdrkgepadsdopsntdlom) |
| `table` | chaîne | Oui | Le nom de la table Supabase à rechercher |
| `column` | chaîne | Oui | La colonne dans laquelle rechercher |
| `query` | chaîne | Oui | La requête de recherche |
| `searchType` | chaîne | Non | Type de recherche : plain, phrase ou websearch (par défaut : websearch) |
| `language` | chaîne | Non | Langue pour la configuration de recherche textuelle (par défaut : english) |
| `limit` | nombre | Non | Nombre maximum de lignes à retourner |
| `apiKey` | chaîne | Oui | Votre clé secrète de rôle de service Supabase |
#### Sortie

View File

@@ -49,40 +49,40 @@ AIブロックを使用するワークフローでは、ログで詳細なコス
<Tabs items={['Hosted Models', 'Bring Your Own API Key']}>
<Tab>
**ホステッドモデル** - Simは2倍の価格乗数でAPIキーを提供します
**ホステッドモデル** - Simは2.5倍の価格倍率でAPIキーを提供します
**OpenAI**
| モデル | 基本価格(入力/出力) | ホステッド価格(入力/出力) |
|-------|---------------------------|----------------------------|
| GPT-5.1 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.50 / $4.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.10 / $0.80 |
| GPT-4o | $2.50 / $10.00 | $5.00 / $20.00 |
| GPT-4.1 | $2.00 / $8.00 | $4.00 / $16.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $0.80 / $3.20 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.20 / $0.80 |
| o1 | $15.00 / $60.00 | $30.00 / $120.00 |
| o3 | $2.00 / $8.00 | $4.00 / $16.00 |
| o4 Mini | $1.10 / $4.40 | $2.20 / $8.80 |
| GPT-5.1 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.63 / $5.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.13 / $1.00 |
| GPT-4o | $2.50 / $10.00 | $6.25 / $25.00 |
| GPT-4.1 | $2.00 / $8.00 | $5.00 / $20.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $1.00 / $4.00 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.25 / $1.00 |
| o1 | $15.00 / $60.00 | $37.50 / $150.00 |
| o3 | $2.00 / $8.00 | $5.00 / $20.00 |
| o4 Mini | $1.10 / $4.40 | $2.75 / $11.00 |
**Anthropic**
| モデル | 基本価格(入力/出力) | ホステッド価格(入力/出力) |
|-------|---------------------------|----------------------------|
| Claude Opus 4.5 | $5.00 / $25.00 | $10.00 / $50.00 |
| Claude Opus 4.1 | $15.00 / $75.00 | $30.00 / $150.00 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.00 / $10.00 |
| Claude Opus 4.5 | $5.00 / $25.00 | $12.50 / $62.50 |
| Claude Opus 4.1 | $15.00 / $75.00 | $37.50 / $187.50 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.50 / $12.50 |
**Google**
| モデル | 基本価格(入力/出力) | ホステッド価格(入力/出力) |
|-------|---------------------------|----------------------------|
| Gemini 3 Pro Preview | $2.00 / $12.00 | $4.00 / $24.00 |
| Gemini 2.5 Pro | $1.25 / $10.00 | $2.50 / $20.00 |
| Gemini 2.5 Flash | $0.30 / $2.50 | $0.60 / $5.00 |
| Gemini 3 Pro Preview | $2.00 / $12.00 | $5.00 / $30.00 |
| Gemini 2.5 Pro | $0.15 / $0.60 | $0.38 / $1.50 |
| Gemini 2.5 Flash | $0.15 / $0.60 | $0.38 / $1.50 |
*2倍の乗数は、インフラストラクチャとAPI管理コストをカバーします。*
*2.5倍の倍率はインフラストラクチャとAPI管理コストをカバーしています。*
</Tab>
<Tab>
@@ -185,12 +185,12 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
サブスクリプションプランによって使用制限が異なります:
| プラン | 月間使用制限 | レート制限(分) |
| プラン | 月間使用制限 | レート制限(分あたり |
|------|-------------------|-------------------------|
| **Free** | $20 | 同期5、非同期10 |
| **Pro** | $100 | 同期10、非同期50 |
| **Team** | $500プール | 同期50、非同期100 |
| **Enterprise** | カスタム | カスタム |
| **無料** | $10 | 5同期、10非同期 |
| **プロ** | $100 | 10同期、50非同期 |
| **チーム** | $500プール | 50同期100非同期 |
| **エンタープライズ** | カスタム | カスタム |
## 課金モデル

View File

@@ -34,87 +34,81 @@ SimはPDF、WordDOC/DOCX、プレーンテキストTXT、Markdown
<Image src="/static/knowledgebase/knowledgebase.png" alt="処理されたコンテンツを表示するドキュメントチャンクビュー" width={800} height={500} />
### チャンク設定
ナレッジベースを作成する際、ドキュメントをチャンクに分割する方法を設定できます。
| 設定 | 単位 | デフォルト | 範囲 | 説明 |
|---------|------|---------|-------|-------------|
| **最大チャンクサイズ** | トークン | 1,024 | 100-4,000 | 各チャンクの最大サイズ1トークン ≈ 4文字 |
| **最小チャンクサイズ** | 文字 | 1 | 1-2,000 | 小さな断片を避けるための最小チャンクサイズ |
| **オーバーラップ** | 文字 | 200 | 0-500 | 連続するチャンク間のコンテキストオーバーラップ |
- **階層的分割**: ドキュメント構造(セクション、段落、文)を尊重
- **デフォルトチャンクサイズ**: 1,024文字
- **設定可能範囲**: チャンクあたり100〜4,000文字
- **スマートオーバーラップ**: コンテキスト保持のためデフォルトで200文字
- **階層的分割**: 文書構造(セクション、段落、文)を尊重
### 編集機能
- **チャンクコンテンツの編集**: 個々のチャンクのテキストコンテンツを変更
- **チャンク境界の調整**: 必要に応じてチャンク結合または分割
- **チャンク内容の編集**: 個々のチャンクのテキスト内容を修正
- **チャンク境界の調整**: 必要に応じてチャンク結合分割
- **メタデータの追加**: 追加のコンテキストでチャンクを強化
- **一括操作**: 複数のチャンクを効率的に管理
## 高度なPDF処理
PDFドキュメントに対して、Simは強化された処理機能を提供します
PDFドキュメントについて、Simは強化された処理機能を提供します
### OCRサポート
Azureまたは[Mistral OCR](https://docs.mistral.ai/ocr/)で設定されている場合:
- **スキャンされたドキュメント処理**: 画像ベースのPDFからテキストを抽出
- **混合コンテンツ処理**: テキストと画像の両方を含むPDFを処理
- **高精度**: 高度なAIモデルにより正確なテキスト抽出を保証
Azureまたは[Mistral OCR](https://docs.mistral.ai/ocr/)で構成されている場合
- **スキャンされたドキュメント処理**: 画像ベースのPDFからテキストを抽出
- **混合コンテンツ処理**: テキストと画像の両方を含むPDFを処理
- **高精度**: 高度なAIモデル正確なテキスト抽出を保証
## ワークフローでのナレッジブロックの使用
ドキュメントが処理されると、ナレッジブロックを通じてAIワークフローで使用できます。これにより検索拡張生成RAGが可能になり、AIエージェントがドキュメントコンテンツにアクセスして推論し、より正確でコンテキストに沿った答を提供できます。
ドキュメントが処理されると、ナレッジブロックを通じてAIワークフローで使用できるようになります。これにより検索拡張生成RAGが可能になり、AIエージェントがドキュメントの内容にアクセスして推論し、より正確でコンテキストに沿った答を提供できます。
<Image src="/static/knowledgebase/knowledgebase-2.png" alt="ワークフローでのナレッジブロックの使用" width={800} height={500} />
### ナレッジブロックの機能
- **セマンティック検索**: 自然言語クエリを使用して関連コンテンツを検索
- **コンテキスト統合**: 関連するチャンクをエージェントプロンプトに自動的に含める
- **動的検索**: ワークフロー実行中にリアルタイムで検索を実行
- **関連性スコアリング**: セマンティック類似によって結果ランク付け
- **意味検索**: 自然言語クエリを使用して関連コンテンツを検索
- **コンテキスト統合**: エージェントプロンプトに関連チャンクを自動的に含める
- **動的検索**: ワークフロー実行中にリアルタイムで検索が行われる
- **関連性スコアリング**: 意味的類似によって結果ランク付け
### 統合オプション
- **システムプロンプト**: AIエージェントにコンテキストを提供
- **動的コンテキスト**: 会話中に関連情報を検索して含める
- **複数ドキュメント検索**: ナレッジベース全体をクエリ
- **フィルター検索**: タグと組み合わせて正確なコンテンツ取得
- **複数ドキュメント検索**: ナレッジベース全体を横断して検索
- **フィルター検索**: タグと組み合わせて正確なコンテンツ検索
## ベクトル検索技術
Simは[pgvector](https://github.com/pgvector/pgvector)を活用したベクトル検索により、コンテンツの意味とコンテキストを理解します
Simは[pgvector](https://github.com/pgvector/pgvector)を活用したベクトル検索を使用して、コンテンツの意味とコンテキストを理解します
### セマンティック理解
- **コンテキスト検索**: 正確なキーワードが一致しない場合でも関連コンテンツを検索
- **概念ベースの取得**: アイデア間の関係を理解
- **多言語サポート**: 異なる言語間で動作
- **同義語認識**: 関連する用語概念を検索
### 意味的理解
- **コンテキスト検索**正確なキーワードが一致しなくても関連コンテンツを見つける
- **概念ベースの検索**アイデア間の関係を理解
- **多言語サポート**異なる言語間で機能
- **同義語認識**関連する用語概念を見つける
### 検索機能
- **自然言語クエリ**: 平易な語で質問
- **類似検索**: 概念的に類似したコンテンツを検索
- **ハイブリッド検索**: ベクトル検索と従来のキーワード検索を組み合わせ
- **設定可能な結果**: 結果の数と関連性のしきい値を制御
- **自然言語クエリ**平易な日本語で質問できる
- **類似検索**概念的に類似したコンテンツを見つける
- **ハイブリッド検索**ベクトル検索と従来のキーワード検索を組み合わせ
- **結果の設定**結果の数と関連性の値を制御
## ドキュメント管理
### 整理機能
- **一括アップロード**: 非同期API経由で複数ファイルを一度にアップロード
- **処理ステータス**: ドキュメント処理のリアルタイム更新
- **検索とフィルター**: 大規模なコレクション内でドキュメントを素早く検索
- **メタデータ追跡**: ファイル情報と処理詳細の自動キャプチャ
- **一括アップロード**非同期APIを通じて複数ファイルを一度にアップロード
- **処理状況**ドキュメント処理のリアルタイム更新
- **検索とフィルタリング**大規模なコレクションからドキュメントを素早く見つける
- **メタデータ追跡**ファイル情報と処理詳細の自動キャプチャ
### セキュリティとプライバシー
- **安全なストレージ**: エンタープライズグレードのセキュリティでドキュメントを保存
- **アクセス制御**: ワークスペースベースの権限
- **処理の分離**: 各ワークスペースは分離されたドキュメント処理を実施
- **データ保持**: ドキュメント保持ポリシー設定
- **安全なストレージ**エンタープライズグレードのセキュリティでドキュメントを保存
- **アクセス制御**ワークスペースベースの権限
- **処理の分離**各ワークスペースは分離されたドキュメント処理を持つ
- **データ保持**ドキュメント保持ポリシー設定
## はじめに
1. **ナレッジベースに移動**: ワークスペースのサイドバーからアクセス
2. **ドキュメントアップロード**: ドラッグアンドドロップまたはファイルを選択してアップロード
3. **処理監視**: ドキュメントが処理されチャンク化される様子を確認
4. **チャンク探索**: 処理されたコンテンツを表示および編集
5. **ワークフロー追加**: Knowledgeブロックを使用してAIエージェントと統合
1. **ナレッジベースに移動**ワークスペースのサイドバーからアクセス
2. **ドキュメントアップロード**ドラッグドロップまたはファイルを選択してアップロード
3. **処理監視**ドキュメントが処理されチャンク化される過程を確認
4. **チャンク探索**処理されたコンテンツを表示編集
5. **ワークフローへの追加**:ナレッジブロックを使用してAIエージェントと統合
ナレッジベースは静的なドキュメントをインテリジェントで検索可能なリソースに変換し、AIワークフローがより情報に基づいた文脈に応じた応答を活用できるようにします。
ナレッジベースは静的なドキュメントを、AIワークフローがより情報に基づいた文脈的な応答のために活用できる、インテリジェントで検索可能なリソースに変換します。

View File

@@ -38,18 +38,16 @@ Intercomをワークフローに統合します。連絡先の作成、取得、
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `role` | string | いいえ | 連絡先の役割。「user」または「lead」を指定可能。未指定の場合は「lead」がデフォルト |
| `email` | string | いいえ | 連絡先のメールアドレス |
| `external_id` | string | いいえ | クライアントが提供する連絡先の一意の識別子 |
| `phone` | string | いいえ | 連絡先の電話番号 |
| `name` | string | いいえ | 連絡先の名前 |
| `avatar` | string | いいえ | 連絡先のアバター画像URL |
| `signed_up_at` | number | いいえ | ユーザーが登録した時刻(Unixタイムスタンプ) |
| `last_seen_at` | number | いいえ | ユーザーが最後に確認された時刻(Unixタイムスタンプ) |
| `signed_up_at` | number | いいえ | ユーザーが登録した時間(Unixタイムスタンプ |
| `last_seen_at` | number | いいえ | ユーザーが最後に確認された時間(Unixタイムスタンプ |
| `owner_id` | string | いいえ | 連絡先のアカウント所有権が割り当てられた管理者のID |
| `unsubscribed_from_emails` | boolean | いいえ | 連絡先がメールの配信停止をしているかどうか |
| `custom_attributes` | string | いいえ | カスタム属性(JSONオブジェクト形式、例:\{"attribute_name": "value"\}) |
| `company_id` | string | いいえ | 作成時に連絡先を関連付ける会社ID |
| `unsubscribed_from_emails` | boolean | いいえ | 連絡先がメールの配信を解除しているかどうか |
| `custom_attributes` | string | いいえ | JSONオブジェクトとしてのカスタム属性(例:\{"attribute_name": "value"\} |
#### 出力
@@ -84,18 +82,15 @@ Intercomの既存の連絡先を更新する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | はい | 更新する連絡先ID |
| `role` | string | いいえ | 連絡先の役割。「user」または「lead」を指定可能 |
| `external_id` | string | いいえ | クライアントが提供する連絡先の一意の識別子 |
| `email` | string | いいえ | 連絡先のメールアドレス |
| `phone` | string | いいえ | 連絡先の電話番号 |
| `name` | string | いいえ | 連絡先の名前 |
| `avatar` | string | いいえ | 連絡先のアバター画像URL |
| `signed_up_at` | number | いいえ | ユーザーが登録した時刻(Unixタイムスタンプ) |
| `last_seen_at` | number | いいえ | ユーザーが最後に確認された時刻(Unixタイムスタンプ) |
| `signed_up_at` | number | いいえ | ユーザーが登録した時間(Unixタイムスタンプ |
| `last_seen_at` | number | いいえ | ユーザーが最後に確認された時間(Unixタイムスタンプ |
| `owner_id` | string | いいえ | 連絡先のアカウント所有権が割り当てられた管理者のID |
| `unsubscribed_from_emails` | boolean | いいえ | 連絡先がメールの配信停止をしているかどうか |
| `custom_attributes` | string | いいえ | カスタム属性(JSONオブジェクト形式、例:\{"attribute_name": "value"\}) |
| `company_id` | string | いいえ | 連絡先を関連付ける会社ID |
| `unsubscribed_from_emails` | boolean | いいえ | 連絡先がメールの配信を解除しているかどうか |
| `custom_attributes` | string | いいえ | JSONオブジェクトとしてのカスタム属性(例:\{"attribute_name": "value"\} |
#### 出力
@@ -133,8 +128,6 @@ Intercomの既存の連絡先を更新する
| `query` | string | はい | 検索クエリ(例:\{"field":"email","operator":"=","value":"user@example.com"\} |
| `per_page` | number | いいえ | ページあたりの結果数最大150 |
| `starting_after` | string | いいえ | ページネーション用カーソル |
| `sort_field` | string | いいえ | ソート対象のフィールド(例:"name"、"created_at"、"last_seen_at" |
| `sort_order` | string | いいえ | ソート順:"ascending"(昇順)または"descending"(降順) |
#### 出力
@@ -173,10 +166,9 @@ Intercomで企業を作成または更新する
| `website` | string | いいえ | 企業のウェブサイト |
| `plan` | string | いいえ | 企業のプラン名 |
| `size` | number | いいえ | 企業の従業員数 |
| `industry` | string | いいえ | 企業が事業を展開る業界 |
| `monthly_spend` | number | いいえ | 企業があなたのビジネスに生み出す収益額。注このフィールドは小数点以下を切り捨てて整数にします155.98は155になります |
| `industry` | string | いいえ | 企業が事業を展開している業界 |
| `monthly_spend` | number | いいえ | 企業があなたのビジネスにもたらす収益額。注このフィールドは小数点以下を切り捨てて整数にします155.98は155になります |
| `custom_attributes` | string | いいえ | JSONオブジェクトとしてのカスタム属性 |
| `remote_created_at` | number | いいえ | あなたが企業を作成した時刻Unixタイムスタンプ |
#### 出力
@@ -212,7 +204,6 @@ IDによってIntercomから単一の企業を取得する
| --------- | ---- | -------- | ----------- |
| `per_page` | number | いいえ | ページあたりの結果数 |
| `page` | number | いいえ | ページ番号 |
| `starting_after` | string | いいえ | ページネーション用カーソル(ページベースのページネーションより推奨) |
#### 出力
@@ -230,8 +221,7 @@ IDによりIntercomから単一の会話を取得
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | はい | 取得する会話ID |
| `display_as` | string | いいえ | プレーンテキストでメッセージを取得する場合は「plaintext」に設定 |
| `include_translations` | boolean | いいえ | trueの場合、会話パーツは会話の検出言語に翻訳されます |
| `display_as` | string | いいえ | プレーンテキストでメッセージを取得するは「plaintext」に設定 |
#### 出力
@@ -250,8 +240,6 @@ IDによりIntercomから単一の会話を取得
| --------- | ---- | -------- | ----------- |
| `per_page` | number | いいえ | ページあたりの結果数最大150 |
| `starting_after` | string | いいえ | ページネーション用カーソル |
| `sort` | string | いいえ | ソートするフィールド「waiting_since」、「updated_at」、「created_at」 |
| `order` | string | いいえ | ソート順「asc」昇順または「desc」降順 |
#### 出力
@@ -270,10 +258,9 @@ IDによりIntercomから単一の会話を取得
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | はい | 返信する会話ID |
| `message_type` | string | はい | メッセージタイプ「comment」または「note」 |
| `body` | string | はい | 返信のテキスト本文 |
| `admin_id` | string | いいえ | 返信を作成する管理者のID。指定しない場合、デフォルトの管理者(Operator/Finが使用されます。 |
| `attachment_urls` | string | いいえ | カンマ区切りの画像URL一覧最大10 |
| `created_at` | number | いいえ | 返信が作成されたときのUnixタイムスタンプ。指定しない場合、現在時刻が使用されます。 |
| `body` | string | はい | 返信の本文テキスト |
| `admin_id` | string | いいえ | 返信を作成する管理者のID。提供されない場合、デフォルトの管理者(オペレーター/Finが使用されます。 |
| `attachment_urls` | string | いいえ | 画像URLのカンマ区切りリスト最大10 |
#### 出力
@@ -292,9 +279,7 @@ IDによりIntercomから単一の会話を取得
| --------- | ---- | -------- | ----------- |
| `query` | string | はい | JSONオブジェクトとしての検索クエリ |
| `per_page` | number | いいえ | ページあたりの結果数最大150 |
| `starting_after` | string | いいえ | ページネーション用カーソル |
| `sort_field` | string | いいえ | ソートするフィールド(例:「created_at」、「updated_at」 |
| `sort_order` | string | いいえ | ソート順:「ascending」または「descending」 |
| `starting_after` | string | いいえ | ページネーション用カーソル |
#### 出力
@@ -314,10 +299,6 @@ Intercomで新しいチケットを作成する
| `ticket_type_id` | string | はい | チケットタイプのID |
| `contacts` | string | はい | 連絡先識別子のJSON配列\[\{"id": "contact_id"\}\] |
| `ticket_attributes` | string | はい | _default_title_と_default_description_を含むチケット属性のJSONオブジェクト |
| `company_id` | string | いいえ | チケットに関連付ける会社ID |
| `created_at` | number | いいえ | チケットが作成された時間Unixタイムスタンプ。提供されない場合、現在時刻が使用されます。 |
| `conversation_to_link_id` | string | いいえ | このチケットにリンクする既存の会話のID |
| `disable_notifications` | boolean | いいえ | trueの場合、チケット作成時の通知を抑制します |
#### 出力
@@ -351,15 +332,13 @@ Intercomで管理者が開始した新しいメッセージを作成して送信
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `message_type` | string | はい | メッセージタイプ:アプリ内メッセージの場合は「inapp」、メールメッセージの場合は「email」 |
| `template` | string | はい | メッセージテンプレートスタイル:プレーンテキストの場合は「plain」、パーソナライズスタイルの場合は「personal」 |
| `message_type` | string | はい | メッセージタイプ「inapp」または「email」 |
| `subject` | string | いいえ | メッセージの件名emailタイプの場合 |
| `body` | string | はい | メッセージの本文 |
| `from_type` | string | はい | 送信者タイプ:「admin」 |
| `from_type` | string | はい | 送信者タイプ「admin」 |
| `from_id` | string | はい | メッセージを送信する管理者のID |
| `to_type` | string | はい | 受信者タイプ:「contact」 |
| `to_type` | string | はい | 受信者タイプ「contact」 |
| `to_id` | string | はい | メッセージを受信する連絡先のID |
| `created_at` | number | いいえ | メッセージが作成された時間Unixタイムスタンプ。提供されない場合、現在時刻が使用されます。 |
#### 出力

View File

@@ -94,16 +94,10 @@ Jira課題を作成する
| `projectId` | string | はい | 課題のプロジェクトID |
| `summary` | string | はい | 課題の要約 |
| `description` | string | いいえ | 課題の説明 |
| `priority` | string | いいえ | 課題の優先度IDまたは名前「10000」または「高」 |
| `assignee` | string | いいえ | 課題の担当者アカウントID |
| `priority` | string | いいえ | 課題の優先度 |
| `assignee` | string | いいえ | 課題の担当者 |
| `cloudId` | string | いいえ | インスタンスのJira Cloud ID。提供されない場合、ドメインを使用して取得されます。 |
| `issueType` | string | はい | 作成する課題のタイプ(例:タスク、ストーリー) |
| `labels` | array | いいえ | 課題のラベル(ラベル名の配列) |
| `duedate` | string | いいえ | 課題の期限形式YYYY-MM-DD |
| `reporter` | string | いいえ | 課題の報告者アカウントID |
| `environment` | string | いいえ | 課題の環境情報 |
| `customFieldId` | string | いいえ | カスタムフィールドIDcustomfield_10001 |
| `customFieldValue` | string | いいえ | カスタムフィールドの値 |
#### 出力
@@ -112,8 +106,7 @@ Jira課題を作成する
| `ts` | string | 操作のタイムスタンプ |
| `issueKey` | string | 作成された課題キーPROJ-123 |
| `summary` | string | 課題の要約 |
| `url` | string | 作成された課題のURL |
| `assigneeId` | string | 割り当てられたユーザーのアカウントID割り当てられている場合 |
| `url` | string | 作成された課題のURL |
### `jira_bulk_read`
@@ -527,31 +520,7 @@ Jira課題からウォッチャーを削除する
| `issueKey` | string | 課題キー |
| `watcherAccountId` | string | 削除されたウォッチャーのアカウントID |
### `jira_get_users`
## 注意事項
Jiraユーザーを取得します。アカウントIDが提供された場合、単一のユーザーを返します。それ以外の場合、すべてのユーザーのリストを返します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `domain` | string | はい | あなたのJiraドメインyourcompany.atlassian.net |
| `accountId` | string | いいえ | 特定のユーザーを取得するためのオプションのアカウントID。提供されない場合、すべてのユーザーを返します。 |
| `startAt` | number | いいえ | 返す最初のユーザーのインデックスページネーション用、デフォルト0 |
| `maxResults` | number | いいえ | 返すユーザーの最大数デフォルト50 |
| `cloudId` | string | いいえ | インスタンスのJira Cloud ID。提供されない場合、ドメインを使用して取得されます。 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `ts` | string | 操作のタイムスタンプ |
| `users` | json | accountId、displayName、emailAddress、activeステータス、avatarUrlsを含むユーザーの配列 |
| `total` | number | 返されたユーザーの総数 |
| `startAt` | number | ページネーション開始インデックス |
| `maxResults` | number | ページあたりの最大結果数 |
## 注記
- カテゴリ:`tools`
- タイプ:`jira`
- カテゴリー: `tools`
- タイプ: `jira`

View File

@@ -1,6 +1,6 @@
---
title: メモリー
description: メモリストアを追加
description: メモリストアを追加
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -12,22 +12,23 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
## 使用方法
ワークフローにメモリを統合します。メモリの追加、取得、メモリの取得、削除が可能です。
ワークフローにメモリを統合します。メモリの追加、取得、すべてのメモリの取得、メモリーの削除が可能です。
## ツール
### `memory_add`
データベースに新しいメモリを追加するか、同じIDを持つ既存のメモリに追します。
新しいメモリーをデータベースに追加するか、同じID既存のメモリに追します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | いいえ | 会話識別子user-123、session-abc。この会話IDを持つメモリがに存在する場合、新しいメッセージが追記されます。 |
| `conversationId` | string | いいえ | 会話識別子user-123、session-abc。このブロックに対してこの会話IDメモリがすでに存在する場合、新しいメッセージはそれに追加されます。 |
| `id` | string | いいえ | 会話識別子のレガシーパラメータ。代わりにconversationIdを使用してください。後方互換性のために提供されています。 |
| `role` | string | はい | エージェントメモリのロールuser、assistant、またはsystem |
| `role` | string | はい | エージェントメモリの役割user、assistant、またはsystem |
| `content` | string | はい | エージェントメモリのコンテンツ |
| `blockId` | string | いいえ | オプションのブロックID。提供されない場合、実行コンテキストから現在のブロックIDを使用するか、デフォルトで「default」になります。 |
#### 出力
@@ -39,27 +40,29 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
### `memory_get`
会話IDによってメモリを取得します。一致するメモリを返します。
conversationId、blockId、blockName、またはそれらの組み合わせによってメモリを取得します。一致するすべてのメモリを返します。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | いいえ | 会話識別子user-123、session-abcこの会話のメモリを返します。 |
| `conversationId` | string | いいえ | 会話識別子user-123、session-abc単独で提供された場合、すべてのブロックにわたるこの会話のすべてのメモリを返します。 |
| `id` | string | いいえ | 会話識別子のレガシーパラメータ。代わりにconversationIdを使用してください。後方互換性のために提供されています。 |
| `blockId` | string | いいえ | ブロック識別子。単独で提供された場合、すべての会話にわたるこのブロックのすべてのメモリを返します。conversationIdと一緒に提供された場合、そのブロック内の特定の会話のメモリを返します。 |
| `blockName` | string | いいえ | ブロック名。blockIdの代替。単独で提供された場合、この名前を持つブロックのすべてのメモリを返します。conversationIdと一緒に提供された場合、この名前を持つブロック内のその会話のメモリを返します。 |
#### 出力
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | メモリが正常に取得されたかどうか |
| `memories` | array | conversationIddataフィールドを含むメモリオブジェクトの配列 |
| `memories` | array | conversationId、blockId、blockName、およびdataフィールドを含むメモリオブジェクトの配列 |
| `message` | string | 成功またはエラーメッセージ |
| `error` | string | 操作が失敗した場合のエラーメッセージ |
### `memory_get_all`
データベースからすべてのメモリを取得しま
データベースからすべてのメモリを取得す
#### 入力
@@ -71,20 +74,22 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| パラメータ | 型 | 説明 |
| --------- | ---- | ----------- |
| `success` | boolean | すべてのメモリが正常に取得されたかどうか |
| `memories` | array | key、conversationId、dataフィールドを含むすべてのメモリオブジェクトの配列 |
| `memories` | array | key、conversationId、blockId、blockName、およびdataフィールドを含むすべてのメモリオブジェクトの配列 |
| `message` | string | 成功またはエラーメッセージ |
| `error` | string | 操作が失敗した場合のエラーメッセージ |
### `memory_delete`
conversationIdによってメモリを削除します。
conversationId、blockId、blockName、またはそれらの組み合わせによってメモリを削除します。一括削除をサポートしています。
#### 入力
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | いいえ | 会話識別子user-123、session-abc。この会話のすべてのメモリを削除します。 |
| `conversationId` | string | いいえ | 会話識別子user-123、session-abc単独で提供された場合、すべてのブロックにわたるこの会話のすべてのメモリを削除します。 |
| `id` | string | いいえ | 会話識別子のレガシーパラメータ。代わりにconversationIdを使用してください。後方互換性のために提供されています。 |
| `blockId` | string | いいえ | ブロック識別子。単独で提供された場合、すべての会話にわたるこのブロックのすべてのメモリを削除します。conversationIdと共に提供された場合、そのブロック内の特定の会話のメモリを削除します。 |
| `blockName` | string | いいえ | ブロック名。blockIdの代替。単独で提供された場合、この名前を持つブロックのすべてのメモリを削除します。conversationIdと共に提供された場合、この名前を持つブロック内のその会話のメモリを削除します。 |
#### 出力

View File

@@ -110,8 +110,8 @@ Slackチャンネルから最新のメッセージを読み取ります。フィ
| `authMethod` | string | いいえ | 認証方法oauthまたはbot_token |
| `botToken` | string | いいえ | カスタムボット用のボットトークン |
| `channel` | string | いいえ | メッセージを読み取るSlackチャンネル#general |
| `userId` | string | いいえ | DM会話用のユーザーIDU1234567890 |
| `limit` | number | いいえ | 取得するメッセージ数デフォルト10、最大15 |
| `userId` | string | いいえ | DM会話用のユーザーIDU1234567890 |
| `limit` | number | いいえ | 取得するメッセージ数デフォルト10、最大100 |
| `oldest` | string | いいえ | 時間範囲の開始(タイムスタンプ) |
| `latest` | string | いいえ | 時間範囲の終了(タイムスタンプ) |

View File

@@ -48,8 +48,7 @@ Supabaseテーブルからデータを照会する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | クエリするSupabaseテーブルの名前 |
| `schema` | string | いいえ | クエリするデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用します。 |
| `table` | string | はい | 照会するSupabaseテーブルの名前 |
| `filter` | string | いいえ | PostgRESTフィルター"id=eq.123" |
| `orderBy` | string | いいえ | 並べ替える列降順の場合はDESCを追加 |
| `limit` | number | いいえ | 返す最大行数 |
@@ -72,7 +71,6 @@ Supabaseテーブルにデータを挿入する
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | データを挿入するSupabaseテーブルの名前 |
| `schema` | string | いいえ | 挿入するデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用します。 |
| `data` | array | はい | 挿入するデータ(オブジェクトの配列または単一のオブジェクト) |
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
@@ -91,11 +89,10 @@ Supabaseテーブルにデータを挿入する
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | クエリするSupabaseテーブルの名前 |
| `schema` | string | いいえ | クエリするデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用します。 |
| `projectId` | string | はい | SupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | クエリを実行するSupabaseテーブルの名前 |
| `filter` | string | はい | 特定の行を見つけるためのPostgRESTフィルター"id=eq.123" |
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
| `apiKey` | string | はい | Supabaseサービスロールシークレットキー |
#### 出力
@@ -114,8 +111,7 @@ Supabaseテーブルにデータを挿入する
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | 更新するSupabaseテーブルの名前 |
| `schema` | string | いいえ | 更新するデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用します。 |
| `filter` | string | はい | 更新する行を識別するためのPostgRESTフィルター"id=eq.123" |
| `filter` | string | い | 更新する行を識別するPostgRESTフィルター"id=eq.123" |
| `data` | object | はい | 一致する行で更新するデータ |
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
@@ -136,8 +132,7 @@ Supabaseテーブルにデータを挿入する
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | 削除するSupabaseテーブルの名前 |
| `schema` | string | いいえ | 削除するデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用します。 |
| `filter` | string | はい | 削除する行を識別するためのPostgRESTフィルター"id=eq.123" |
| `filter` | string | い | 削除する行を識別するPostgRESTフィルター"id=eq.123" |
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
#### 出力
@@ -156,9 +151,8 @@ Supabaseテーブルにデータを挿入または更新するアップサー
| パラメータ | 型 | 必須 | 説明 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | データをupsertするSupabaseテーブルの名前 |
| `schema` | string | いいえ | upsertするデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用します。 |
| `data` | array | はい | upsert挿入または更新するデータオブジェクトの配列または単一のオブジェクト |
| `table` | string | はい | データをアップサートするSupabaseテーブルの名前 |
| `data` | array | はい | アップサート(挿入または更新)するデータ - オブジェクトの配列または単一のオブジェクト |
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
#### 出力
@@ -178,7 +172,6 @@ Supabaseテーブルの行数をカウントする
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | 行数をカウントするSupabaseテーブルの名前 |
| `schema` | string | いいえ | カウント元のデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用 |
| `filter` | string | いいえ | PostgRESTフィルター"status=eq.active" |
| `countType` | string | いいえ | カウントタイプexact、planned、またはestimatedデフォルトexact |
| `apiKey` | string | はい | あなたのSupabaseサービスロールシークレットキー |
@@ -200,7 +193,6 @@ Supabaseテーブルで全文検索を実行する
| --------- | ---- | -------- | ----------- |
| `projectId` | string | はい | あなたのSupabaseプロジェクトIDjdrkgepadsdopsntdlom |
| `table` | string | はい | 検索するSupabaseテーブルの名前 |
| `schema` | string | いいえ | 検索するデータベーススキーマデフォルトpublic。他のスキーマのテーブルにアクセスする場合に使用 |
| `column` | string | はい | 検索する列 |
| `query` | string | はい | 検索クエリ |
| `searchType` | string | いいえ | 検索タイプplain、phrase、またはwebsearchデフォルトwebsearch |

View File

@@ -47,42 +47,42 @@ totalCost = baseExecutionCharge + modelCost
## 定价选项
<Tabs items={[ '托管模型', '自带 API 密钥' ]}>
<Tabs items={['托管模型', '使用您自己的 API 密钥']}>
<Tab>
**托管模型** - Sim 提供 API 密钥,价格为基础价格的 2 倍:
**托管模型** - Sim 提供 API 密钥,价格为基础价格的 2.5 倍:
**OpenAI**
| 模型 | 基础价格(输入/输出) | 托管价格(输入/输出) |
|-------|---------------------------|----------------------------|
| GPT-5.1 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 | $1.25 / $10.00 | $2.50 / $20.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.50 / $4.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.10 / $0.80 |
| GPT-4o | $2.50 / $10.00 | $5.00 / $20.00 |
| GPT-4.1 | $2.00 / $8.00 | $4.00 / $16.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $0.80 / $3.20 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.20 / $0.80 |
| o1 | $15.00 / $60.00 | $30.00 / $120.00 |
| o3 | $2.00 / $8.00 | $4.00 / $16.00 |
| o4 Mini | $1.10 / $4.40 | $2.20 / $8.80 |
| GPT-5.1 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 | $1.25 / $10.00 | $3.13 / $25.00 |
| GPT-5 Mini | $0.25 / $2.00 | $0.63 / $5.00 |
| GPT-5 Nano | $0.05 / $0.40 | $0.13 / $1.00 |
| GPT-4o | $2.50 / $10.00 | $6.25 / $25.00 |
| GPT-4.1 | $2.00 / $8.00 | $5.00 / $20.00 |
| GPT-4.1 Mini | $0.40 / $1.60 | $1.00 / $4.00 |
| GPT-4.1 Nano | $0.10 / $0.40 | $0.25 / $1.00 |
| o1 | $15.00 / $60.00 | $37.50 / $150.00 |
| o3 | $2.00 / $8.00 | $5.00 / $20.00 |
| o4 Mini | $1.10 / $4.40 | $2.75 / $11.00 |
**Anthropic**
| 模型 | 基础价格(输入/输出) | 托管价格(输入/输出) |
|-------|---------------------------|----------------------------|
| Claude Opus 4.5 | $5.00 / $25.00 | $10.00 / $50.00 |
| Claude Opus 4.1 | $15.00 / $75.00 | $30.00 / $150.00 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $6.00 / $30.00 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.00 / $10.00 |
| Claude Opus 4.5 | $5.00 / $25.00 | $12.50 / $62.50 |
| Claude Opus 4.1 | $15.00 / $75.00 | $37.50 / $187.50 |
| Claude Sonnet 4.5 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Sonnet 4.0 | $3.00 / $15.00 | $7.50 / $37.50 |
| Claude Haiku 4.5 | $1.00 / $5.00 | $2.50 / $12.50 |
**Google**
| 模型 | 基础价格(输入/输出) | 托管价格(输入/输出) |
|-------|---------------------------|----------------------------|
| Gemini 3 Pro Preview | $2.00 / $12.00 | $4.00 / $24.00 |
| Gemini 2.5 Pro | $1.25 / $10.00 | $2.50 / $20.00 |
| Gemini 2.5 Flash | $0.30 / $2.50 | $0.60 / $5.00 |
| Gemini 3 Pro Preview | $2.00 / $12.00 | $5.00 / $30.00 |
| Gemini 2.5 Pro | $0.15 / $0.60 | $0.38 / $1.50 |
| Gemini 2.5 Flash | $0.15 / $0.60 | $0.38 / $1.50 |
*2 倍系数涵盖了基础设施和 API 管理成本。*
*2.5 倍的价格倍增用于覆盖基础设施和 API 管理成本。*
</Tab>
<Tab>
@@ -185,12 +185,12 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
不同的订阅计划有不同的使用限制:
| 方案 | 每月使用限 | 速率限制(每分钟) |
| 计划 | 每月使用限 | 速率限制(每分钟) |
|------|-------------------|-------------------------|
| **Free** | $20 | 5 sync10 async |
| **Pro** | $100 | 10 sync50 async |
| **Team** | $500共享 | 50 sync100 async |
| **Enterprise** | 定制 | 定制 |
| **免费** | $10 | 5 同步10 异步 |
| **专业** | $100 | 10 同步50 异步 |
| **团队** | $500共享 | 50 同步100 异步 |
| **企业** | 自定义 | 自定义 |
## 计费模式

View File

@@ -34,87 +34,81 @@ Sim 支持 PDF、Word (DOC/DOCX)、纯文本 (TXT)、Markdown (MD)、HTML、Exce
<Image src="/static/knowledgebase/knowledgebase.png" alt="显示已处理内容的文档分块视图" width={800} height={500} />
### 分块配置
在创建知识库时,您可以配置文档如何被拆分为多个分块:
| 设置 | 单位 | 默认值 | 范围 | 说明 |
|---------|------|---------|-------|-------------|
| **最大分块大小** | tokens | 1,024 | 100-4,000 | 每个分块的最大大小1 token ≈ 4 个字符) |
| **最小分块大小** | 字符 | 1 | 1-2,000 | 避免生成过小分块的最小分块大小 |
| **重叠量** | 字符 | 200 | 0-500 | 相邻分块之间的上下文重叠字符数 |
- **默认分块大小**1,024 个字符
- **可配置范围**:每块 100-4,000 个字符
- **智能重叠**:默认重叠 200 个字符以保留上下文
- **分层拆分**:遵循文档结构(章节、段落、句子)
### 编辑功能
- **编辑分块内容**修改单个分块的文本内容
- **编辑分块内容**:修改单个分块的文本内容
- **调整分块边界**:根据需要合并或拆分分块
- **添加元数据**:为分块补充更多上下文信息
- **添加元数据**:为分块添加额外的上下文信息
- **批量操作**:高效管理多个分块
## 高级 PDF 处理
对于 PDF 文档Sim 提供增强的处理能
对于 PDF 文档Sim 提供增强的处理能:
### OCR 支持
当配置了 Azure 或 [Mistral OCR](https://docs.mistral.ai/ocr/) 时:
- **扫描文档处理**:从基于图像的 PDF 中提取文本
- **混合内容处理**:处理同时包含文本和图的 PDF
- **高精度**:先进的 AI 模型确保文本提取的准确性
- **混合内容处理**:处理同时包含文本和图的 PDF
- **高精度**:先进的 AI 模型确保准确的文本提取
## 在工作流中使用知识块
文档处理完成后,您可以通过知识块在 AI 工作流中使用它们。这实现了 RAG检索增强生成,让您的 AI 智能体能够访问并理文档内容,从而提供更准确、有上下文的回复
一旦您的文档处理,您可以通过知识块在 AI 工作流中使用它们。这使得检索增强生成RAG成为可能,让您的 AI 代理能够访问并理文档内容,从而提供更准确、有上下文的响应
<Image src="/static/knowledgebase/knowledgebase-2.png" alt="在工作流中使用知识块" width={800} height={500} />
### 知识块功能
- **语义搜索**通过自然语言查询查找相关内容
- **上下文集成**:自动将相关分块纳入智能体提示
- **动态检索**:在工作流执行实时搜索
- **相关性评分**:根据语义相似对结果进行排
- **语义搜索**使用自然语言查询查找相关内容
- **上下文集成**:自动将相关分块包含在代理提示
- **动态检索**:在工作流执行期间实时搜索
- **相关性评分**:根据语义相似对结果进行排
### 集成选项
- **系统提示**:为的 AI 智能体提供上下文
- **动态上下文**:在对话中搜索并纳入相关信息
- **多文档搜索**在整个知识库中查询
- **筛选搜索**:结合标签实现精内容检索
- **系统提示**:为的 AI 代理提供上下文
- **动态上下文**:在对话中搜索并包含相关信息
- **多文档搜索**:在整个知识库中查询
- **过滤搜索**:结合标签实现精内容检索
## 向量搜索技术
Sim 利用 [pgvector](https://github.com/pgvector/pgvector) 提供的向量搜索理解的内容的含义和上下文:
Sim 使用由 [pgvector](https://github.com/pgvector/pgvector) 提供支持的向量搜索理解的内容的含义和上下文:
### 语义理解
- **上下文搜索**:即使关键词不完全匹配,也能找到相关内容
- **基于概念的检索**:理解不同想法之间的关系
- **多语言支持**可跨多种语言使用
- **同义词识别**发现相关术语和概念
- **上下文搜索**:即使精确的关键词不匹配,也能找到相关内容
- **基于概念的检索**:理解想法之间的关系
- **多语言支持**支持跨不同语言工作
- **同义词识别**找到相关术语和概念
### 搜索能
- **自然语言查询**用简单英文提问
- **相似搜索**找概念上相似的内容
- **混合搜索**:结合向量传统关键词搜索
- **结果可配置**控制结果数量和相关性阈值
### 搜索
- **自然语言查询**:用简单的英语提问
- **相似搜索**:找概念上相似的内容
- **混合搜索**:结合向量传统关键词搜索
- **可配置结果**:控制结果数量和相关性阈值
## 文档管理
### 组织功能
- **批量上传**:通过异步 API 一次上传多个文件
- **处理状态**:实时更新文档处理进度
- **搜索与筛选**:在大型集合中快速找文档
- **元数据踪**:自动记录文件信息和处理详情
- **处理状态**:实时更新文档处理状态
- **搜索和过滤**:在大型集合中快速找文档
- **元数据踪**:自动捕获文件信息和处理详情
### 安全隐私
- **安全存储**:文档采用企业级安全存储
- **访问控制**:基于工作区的权限管理
- **处理隔离**:每个工作区的文档处理相互隔离
- **数据保留**配置文档保留策略
### 安全性和隐私
- **安全存储**:文档企业级安全存储
- **访问控制**:基于工作区的权限设置
- **处理隔离**:每个工作区的文档处理是独立的
- **数据保留**:配置文档保留策略
## 快速开始
## 快速入门
1. **进入你的知识库**可在工作区侧边栏访问
2. **上传文档**:拖或选择文件上传
3. **监控处理进度**实时查看文档处理分块
4. **浏览分块内容**:查看编辑处理内容
5. **添加到工作流**:使用 Knowledge 模块集成到你的 AI 智能体
1. **导航到您的知识库**工作区侧边栏访问
2. **上传文档**:拖或选择文件进行上传
3. **监控处理**:查看文档处理分块进度
4. **探索分块**:查看编辑处理后的内容
5. **添加到工作流**:使用知识块与您的 AI 代理集成
知识库将您的静态文档转化为智能、可搜索的资源,使您的 AI 工作流能够利用这些信息,提供更有见地和更具上下文的应。
知识库将您的静态文档转化为智能、可搜索的资源,使您的 AI 工作流能够利用这些资源提供更有信息量和上下文的应。

View File

@@ -38,18 +38,16 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `role` | string | 否 | 联系人角色。可选 'user' 或 'lead'。如未指定,默认为 'lead'。 |
| `email` | string | 否 | 联系人邮箱地址 |
| `external_id` | string | 否 | 客户端为联系人提供的唯一标识符 |
| `phone` | string | 否 | 联系人电话号码 |
| `name` | string | 否 | 联系人姓名 |
| `avatar` | string | 否 | 联系人头像图片 URL |
| `email` | string | 否 | 联系人的电子邮件地址 |
| `external_id` | string | 否 | 客户提供的联系人的唯一标识符 |
| `phone` | string | 否 | 联系人的电话号码 |
| `name` | string | 否 | 联系人的姓名 |
| `avatar` | string | 否 | 联系人的头像图片 URL |
| `signed_up_at` | number | 否 | 用户注册时间Unix 时间戳) |
| `last_seen_at` | number | 否 | 用户最后一次在线时间Unix 时间戳) |
| `owner_id` | string | 否 | 被分配为联系人账户所有者的管理员 ID |
| `unsubscribed_from_emails` | boolean | 否 | 联系人是否已退订邮件 |
| `custom_attributes` | string | 否 | 自定义属性,格式为 JSON 对象(如:\{"attribute_name": "value"\} |
| `company_id` | string | 否 | 创建联系人时关联的公司 ID |
| `last_seen_at` | number | 否 | 用户上次访问时间Unix 时间戳) |
| `owner_id` | string | 否 | 被分配为联系人账户所有者的管理员 ID |
| `unsubscribed_from_emails` | boolean | 否 | 联系人是否取消订阅电子邮件 |
| `custom_attributes` | string | 否 | 自定义属性,格式为 JSON 对象 \(例如,\{"attribute_name": "value"\}\) |
#### 输出
@@ -84,18 +82,15 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `contactId` | string | 是 | 要更新的联系人 ID |
| `role` | string | 否 | 联系人角色。可选 'user' 或 'lead'。 |
| `external_id` | string | 否 | 客户端为联系人提供的唯一标识符 |
| `email` | string | 否 | 联系人邮箱地址 |
| `phone` | string | 否 | 联系人电话号码 |
| `name` | string | 否 | 联系人姓名 |
| `avatar` | string | 否 | 联系人头像图片 URL |
| `email` | string | 否 | 联系人的电子邮件地址 |
| `phone` | string | 否 | 联系人的电话号码 |
| `name` | string | 否 | 联系人的姓名 |
| `avatar` | string | 否 | 联系人的头像图片 URL |
| `signed_up_at` | number | 否 | 用户注册时间Unix 时间戳) |
| `last_seen_at` | number | 否 | 用户最后一次在线时间Unix 时间戳) |
| `owner_id` | string | 否 | 分配为该联系人账户所有的管理员 ID |
| `unsubscribed_from_emails` | boolean | 否 | 联系人是否已退订邮件 |
| `custom_attributes` | string | 否 | 自定义属性,格式为 JSON 对象如:\{"attribute_name": "value"\} |
| `company_id` | string | 否 | 关联的公司 ID |
| `last_seen_at` | number | 否 | 用户上次访问时间Unix 时间戳) |
| `owner_id` | string | 否 | 分配账户所有的管理员 ID |
| `unsubscribed_from_emails` | boolean | 否 | 联系人是否取消订阅电子邮件 |
| `custom_attributes` | string | 否 | 自定义属性,格式为 JSON 对象 \(例如:\{"attribute_name": "value"\}\) |
#### 输出
@@ -128,13 +123,11 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
#### 输入
| 参数 | 类型 | 必 | 描述 |
| 参数 | 类型 | 必 | 描述 |
| --------- | ---- | -------- | ----------- |
| `query` | string | 是 | 搜索查询(例如:\{"field":"email", "operator":"=", "value":"user@example.com"\} |
| `per_page` | number | 否 | 每页结果数量最大值150 |
| `starting_after` | string | 否 | 用于分页游标 |
| `sort_field` | string | 否 | 排序字段(例如:"name""created_at""last_seen_at" |
| `sort_order` | string | 否 | 排序方式“ascending” 或 “descending” |
| `query` | string | 是 | 搜索查询 \(例如, \{"field":"email","operator":"=","value":"user@example.com"\}\) |
| `per_page` | number | 否 | 每页结果数量 \(最大值: 150\) |
| `starting_after` | string | 否 | 分页游标 |
#### 输出
@@ -166,17 +159,16 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
#### 输入
| 参数 | 类型 | 必 | 描述 |
| 参数 | 类型 | 必 | 描述 |
| --------- | ---- | -------- | ----------- |
| `company_id` | string | 是 | 您公司设置的唯一标识符 |
| `name` | string | 否 | 公司名称 |
| `company_id` | string | 是 | 您公司唯一标识符 |
| `name` | string | 否 | 公司名称 |
| `website` | string | 否 | 公司网站 |
| `plan` | string | 否 | 公司套餐名称 |
| `plan` | string | 否 | 公司计划名称 |
| `size` | number | 否 | 公司员工数量 |
| `industry` | string | 否 | 公司所属行业 |
| `monthly_spend` | number | 否 | 公司为您的业务带来的收入。注意:此字段会将浮点数截断为整数(例如155.98 变为 155 |
| `custom_attributes` | string | 否 | 自定义属性,格式为 JSON 对象 |
| `remote_created_at` | number | 否 | 您创建公司时的 Unix 时间戳 |
| `monthly_spend` | number | 否 | 公司为您的业务创造的收入。注意:此字段会将浮点数截断为整数(例如155.98 变为 155 |
| `custom_attributes` | string | 否 | 为 JSON 对象的自定义属性 |
#### 输出
@@ -208,11 +200,10 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
#### 输入
| 参数 | 类型 | 必 | 描述 |
| 参数 | 类型 | 必 | 描述 |
| --------- | ---- | -------- | ----------- |
| `per_page` | number | 否 | 每页结果数量 |
| `page` | number | 否 | 页码 |
| `starting_after` | string | 否 | 分页游标(优先于基于页码的分页) |
| `per_page` | 数字 | 否 | 每页结果数量 |
| `page` | 数字 | 否 | 页码 |
#### 输出
@@ -229,9 +220,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | 是 | 要检索的会话 ID |
| `display_as` | string | 否 | 设为 "plaintext" 检索纯文本消息 |
| `include_translations` | boolean | 否 | 若为 true会话内容将被翻译为检测到的会话语言 |
| `conversationId` | 字符串 | 是 | 要检索的会话 ID |
| `display_as` | 字符串 | 否 | 设为 "plaintext" 检索纯文本消息 |
#### 输出
@@ -248,10 +238,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `per_page` | number | 否 | 每页结果数量最大值150 |
| `starting_after` | string | 否 | 分页游标 |
| `sort` | string | 否 | 排序字段(例如:"waiting_since"、"updated_at"、"created_at" |
| `order` | string | 否 | 排序方式:"asc"(升序)或 "desc"(降序) |
| `per_page` | 数字 | 否 | 每页结果数量 \(最大值: 150\) |
| `starting_after` | 字符串 | 否 | 分页游标 |
#### 输出
@@ -270,10 +258,9 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | 是 | 要回复的会话 ID |
| `message_type` | string | 是 | 消息类型:"comment" 或 "note" |
| `body` | string | 是 | 回复正文 |
| `admin_id` | string | 否 | 回复管理员 ID。如果未提供将使用默认管理员Operator/Fin。 |
| `attachment_urls` | string | 否 | 逗号分隔的图片 URL 列表(最多 10 个) |
| `created_at` | number | 否 | 回复创建时的 Unix 时间戳。如果未提供,则使用当前时间。 |
| `body` | string | 是 | 回复正文文本 |
| `admin_id` | string | 否 | 撰写回复管理员 ID。如果未提供将使用默认管理员Operator/Fin。 |
| `attachment_urls` | string | 否 | 逗号分隔的图片 URL 列表(最多 10 个) |
#### 输出
@@ -288,13 +275,11 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
#### 输入
| 参数 | 类型 | 必 | 描述 |
| 参数 | 类型 | 必 | 描述 |
| --------- | ---- | -------- | ----------- |
| `query` | string | 是 | 作为 JSON 对象的搜索查询 |
| `per_page` | number | 否 | 每页结果数量最大值150 |
| `starting_after` | string | 否 | 分页游标 |
| `sort_field` | string | 否 | 排序字段(例如:"created_at""updated_at" |
| `sort_order` | string | 否 | 排序顺序“ascending” 或 “descending” |
| `starting_after` | string | 否 | 用于分页游标 |
#### 输出
@@ -309,15 +294,11 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
#### 输入
| 参数 | 类型 | 必 | 描述 |
| 参数 | 类型 | 必 | 描述 |
| --------- | ---- | -------- | ----------- |
| `ticket_type_id` | string | 是 | 工单类型的 ID |
| `contacts` | string | 是 | 联系人标识符的 JSON 数组(例如\[\{"id": "contact_id"\}\] |
| `ticket_attributes` | string | 是 | 包含 _default_title_ 和 _default_description_ 的工单属性 JSON 对象 |
| `company_id` | string | 否 | 要关联工单的公司 ID |
| `created_at` | number | 否 | 工单创建时的 Unix 时间戳。如果未提供,则使用当前时间。 |
| `conversation_to_link_id` | string | 否 | 要关联到此工单的现有会话 ID |
| `disable_notifications` | boolean | 否 | 若为 true创建工单时将不发送通知 |
| `contacts` | string | 是 | 联系人标识符的 JSON 数组(例如\[\{"id": "contact_id"\}\] |
| `ticket_attributes` | string | 是 | 包含工单属性的 JSON 对象,包括 _default_title_ 和 _default_description_ |
#### 输出
@@ -351,15 +332,13 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `message_type` | string | 是 | 消息类型:inapp” 表示应用内消息“email” 表示电子邮件消息 |
| `template` | string | | 消息模板样式“plain” 表示纯文本“personal” 表示个性化样式 |
| `subject` | string | 否 | 消息主题(仅适用于 email 类型) |
| `message_type` | string | 是 | 消息类型:"inapp" 或 "email" |
| `subject` | string | | 消息主题(针对 email 类型) |
| `body` | string | 是 | 消息正文 |
| `from_type` | string | 是 | 发送类型:admin |
| `from_type` | string | 是 | 发送类型:"admin" |
| `from_id` | string | 是 | 发送消息的管理员 ID |
| `to_type` | string | 是 | 接收类型:contact |
| `to_type` | string | 是 | 接收类型:"contact" |
| `to_id` | string | 是 | 接收消息的联系人的 ID |
| `created_at` | number | 否 | 消息创建时的 Unix 时间戳。如果未提供,则使用当前时间。 |
#### 输出

View File

@@ -91,19 +91,13 @@ Jira 的主要功能包括:
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `domain` | 字符串 | 是 | 您的 Jira 域名 \(例如yourcompany.atlassian.net\) |
| `projectId` | 字符串 | 是 | 问题所属项目 ID |
| `summary` | 字符串 | 是 | 问题摘要 |
| `description` | 字符串 | 否 | 问题描述 |
| `priority` | 字符串 | 否 | 问题优先级 ID 或名称 \(例如“10000”或“High”\) |
| `assignee` | 字符串 | 否 | 问题负责人账户 ID |
| `cloudId` | 字符串 | 否 | 实例的 Jira Cloud ID。如果未提供将使用域名获取。 |
| `issueType` | 字符串 | 是 | 要创建的问题类型 \(例如:Task、Story\) |
| `labels` | 数组 | 否 | 问题标签 \(标签名称数组\) |
| `duedate` | 字符串 | 否 | 问题截止日期 \(格式YYYY-MM-DD\) |
| `reporter` | 字符串 | 否 | 问题报告人账户 ID |
| `environment` | 字符串 | 否 | 问题环境信息 |
| `customFieldId` | 字符串 | 否 | 自定义字段 ID \(例如customfield_10001\) |
| `customFieldValue` | 字符串 | 否 | 自定义字段的值 |
| `projectId` | 字符串 | 是 | 问题项目 ID |
| `summary` | 字符串 | 是 | 问题摘要 |
| `description` | 字符串 | 否 | 问题描述 |
| `priority` | 字符串 | 否 | 问题优先级 |
| `assignee` | 字符串 | 否 | 问题负责人 |
| `cloudId` | 字符串 | 否 | 实例的 Jira ID。如果未提供将使用域名获取。 |
| `issueType` | 字符串 | 是 | 要创建的问题类型 \(例如:任务、故事\) |
#### 输出
@@ -113,7 +107,6 @@ Jira 的主要功能包括:
| `issueKey` | 字符串 | 创建的问题键 \(例如PROJ-123\) |
| `summary` | 字符串 | 问题摘要 |
| `url` | 字符串 | 创建的问题的 URL |
| `assigneeId` | 字符串 | 已分配用户的账户 ID如已分配 |
### `jira_bulk_read`
@@ -527,31 +520,7 @@ Jira 的主要功能包括:
| `issueKey` | string | 问题键 |
| `watcherAccountId` | string | 移除的观察者账户 ID |
### `jira_get_users`
## 注意事项
获取 Jira 用户。如果提供了账户 ID则返回单个用户否则返回所有用户的列表。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `domain` | 字符串 | 是 | 您的 Jira 域名 \(例如yourcompany.atlassian.net\) |
| `accountId` | 字符串 | 否 | 可选账户 ID用于获取特定用户。如果未提供则返回所有用户。 |
| `startAt` | 数字 | 否 | 要返回的第一个用户的索引 \(用于分页默认值0\) |
| `maxResults` | 数字 | 否 | 要返回的最大用户数 \(默认值50\) |
| `cloudId` | 字符串 | 否 | 实例的 Jira Cloud ID。如果未提供将使用域名获取。 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `ts` | 字符串 | 操作的时间戳 |
| `users` | json | 用户数组,包含 accountId、displayName、emailAddress、active 状态和 avatarUrls |
| `total` | 数字 | 返回的用户总数 |
| `startAt` | 数字 | 分页起始索引 |
| `maxResults` | 数字 | 每页最大结果数 |
## 备注
- 分类:`tools`
- 类型:`jira`
- 类别: `tools`
- 类型: `jira`

View File

@@ -1,6 +1,6 @@
---
title: 内存
description: 添加记忆存储
description: 添加内存存储
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -12,50 +12,53 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
## 使用说明
Memory 集成到工作流程中。可以添加、获取单条记忆、获取所有记忆以及删除记忆。
记忆集成到工作流程中。可以添加记忆、获取记忆、获取所有记忆以及删除记忆。
## 工具
### `memory_add`
向数据库添加新记忆,或将内容追加到有相同 ID 的记忆中。
向数据库添加新的内存,或将数据追加到有相同 ID 的现有内存中。
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | ---- | ----------- |
| `conversationId` | string | 否 | 会话标识符(user-123session-abc。如果已存在该 conversationId 的记忆,则新消息会追加到其中。 |
| `id` | string | 否 | 旧版会话标识参数。请使用 conversationId,保留用于兼容性。 |
| `role` | string | 是 | agent 记忆的角色user、assistant 或 system |
| `content` | string | 是 | agent 记忆的内容 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | 否 | 会话标识符(例如,user-123session-abc。如果此 block 已存在具有该 conversationId 的内存,新消息将附加到该内存中。 |
| `id` | string | 否 | 会话标识符的旧参数。请用 conversationId。为向后兼容而提供。 |
| `role` | string | 是 | 代理内存的角色user、assistant 或 system |
| `content` | string | 是 | 代理内存的内容 |
| `blockId` | string | 否 | 可选的 block ID。如果未提供将使用执行上下文中的当前 block ID或默认为 "default"。 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 记忆是否添加成功 |
| `memories` | array | 包含新或更新记忆的记忆对象数组 |
| `error` | string | 操作失败时的错误信息 |
| `success` | 布尔值 | 是否成功添加了内存 |
| `memories` | 数组 | 包含新添加或更新内存的内存对象数组 |
| `error` | 字符串 | 如果操作失败,显示错误信息 |
### `memory_get`
根据 conversationId 检索记忆,返回匹配的记忆内容
通过 conversationId、blockId、blockName 或其组合检索内存。返回所有匹配的内存
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | ---- | ----------- |
| `conversationId` | string | 否 | 会话标识符(user-123session-abc。返回会话的记忆。 |
| `id` | string | 否 | 旧版会话标识参数。请使用 conversationId,保留用于兼容性。 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | 否 | 会话标识符(例如,user-123session-abc如果单独提供,将返回会话在所有 block 中的所有内存。 |
| `id` | string | 否 | 会话标识符的旧参数。请用 conversationId。为向后兼容而提供。 |
| `blockId` | string | 否 | block 标识符。如果单独提供,将返回此 block 中所有会话的所有内存。如果与 conversationId 一起提供,将返回此 block 中该特定会话的内存。 |
| `blockName` | string | 否 | block 名称。blockId 的替代选项。如果单独提供,将返回具有此名称的 block 的所有内存。如果与 conversationId 一起提供,将返回具有此名称的 block 中该会话的内存。 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 是否成功检索到内存 |
| `memories` | array | 包含 conversationId 和 data 字段的内存对象数组 |
| `success` | boolean | 内存是否成功检索 |
| `memories` | array | 包含 conversationId、blockId、blockName 和 data 字段的内存对象数组 |
| `message` | string | 成功或错误信息 |
| `error` | string | 操作失败的错误信息 |
| `error` | string | 如果操作失败的错误信息 |
### `memory_get_all`
@@ -71,30 +74,32 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 是否成功检索到所有内存 |
| `memories` | array | 包含 key、conversationId 和 data 字段的所有内存对象数组 |
| `memories` | array | 包含 key、conversationId、blockId、blockName 和 data 字段的所有内存对象数组 |
| `message` | string | 成功或错误信息 |
| `error` | string | 操作失败的错误信息 |
| `error` | string | 如果操作失败的错误信息 |
### `memory_delete`
根据 conversationId 删除内存
通过 conversationId、blockId、blockName 或其组合删除内存。支持批量删除
#### 输入
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `conversationId` | string | 否 | 会话标识符(如 user-123session-abc)。将删除会话的所有内存。 |
| `id` | string | 否 | 旧版会话标识参数。请使用 conversationId,保留用于兼容性。 |
| `conversationId` | string | 否 | 会话标识符 \(例如,user-123session-abc\)。如果单独提供,将删除会话在所有块中的所有内存。 |
| `id` | string | 否 | 会话标识符的旧参数。请用 conversationId。为向后兼容而提供。 |
| `blockId` | string | 否 | 块标识符。如果单独提供,将删除此块中所有会话的所有内存。如果与 conversationId 一起提供,将删除此块中特定会话的内存。 |
| `blockName` | string | 否 | 块名称。是 blockId 的替代项。如果单独提供,将删除具有此名称的块的所有内存。如果与 conversationId 一起提供,将删除此名称的块中该会话的内存。 |
#### 输出
| 参数 | 类型 | 描述 |
| --------- | ---- | ----------- |
| `success` | boolean | 是否成功删除内存 |
| `success` | boolean | 内存是否成功删除 |
| `message` | string | 成功或错误信息 |
| `error` | string | 操作失败的错误信息 |
| `error` | string | 如果操作失败的错误信息 |
## 注意事项
## 注意
- 类别:`blocks`
- 类型:`memory`

View File

@@ -109,10 +109,10 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
| `authMethod` | string | 否 | 认证方法oauth 或 bot_token |
| `botToken` | string | 否 | 自定义 Bot 的令牌 |
| `channel` | string | 否 | 要读取消息的 Slack 频道(例如,#general |
| `userId` | string | 否 | DM 话的用户 ID例如U1234567890 |
| `limit` | number | 否 | 要检索的消息数量默认10最大15 |
| `oldest` | string | 否 | 时间范围始(时间戳) |
| `latest` | string | 否 | 时间范围结束(时间戳) |
| `userId` | string | 否 | DM 话的用户 ID例如U1234567890 |
| `limit` | number | 否 | 要检索的消息数量默认10最大100 |
| `oldest` | string | 否 | 时间范围的开始(时间戳) |
| `latest` | string | 否 | 时间范围结束(时间戳) |
#### 输出

View File

@@ -47,13 +47,12 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要查询的 Supabase 表名 |
| `schema` | string | 否 | 要查询的数据库 schema \(默认public\)。用于访问其他 schema 下的表。|
| `filter` | string | 否 | PostgREST 过滤条件 \(例如:"id=eq.123"\) |
| `orderBy` | string | 否 | 排序的列名 \(添加 DESC 表示降序\) |
| `limit` | number | | 返回的最大行数 |
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
| `projectId` | 字符串 | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | 字符串 | 是 | 要查询的 Supabase 表名 |
| `filter` | 字符串 | 否 | PostgREST 过滤条件 \(例如:"id=eq.123"\) |
| `orderBy` | 字符串 | 否 | 排序的列名 \(添加 DESC 表示降序\) |
| `limit` | 数字 | 否 | 返回的最大行数 |
| `apiKey` | 字符串 | | 您的 Supabase 服务角色密钥 |
#### 输出
@@ -72,8 +71,7 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要插入数据的 Supabase 表名 |
| `schema` | string | | 要插入的数据库 schema \(默认public\)。用于访问其他 schema 下的表。|
| `data` | array | 是 | 要插入的数据(对象数组或单个对象)|
| `data` | array | | 要插入的数据 \(对象数组或单个对象\) |
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
#### 输出
@@ -93,8 +91,7 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要查询的 Supabase 表名 |
| `schema` | string | | 要查询的数据库 schema \(默认public\)。用于访问其他 schema 下的表。|
| `filter` | string | 是 | 用于查找特定行的 PostgREST 过滤条件 \(例如:"id=eq.123"\) |
| `filter` | string | | PostgREST 筛选条件以找到特定行 \(例如:"id=eq.123"\) |
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
#### 输出
@@ -113,10 +110,9 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要更新的 Supabase 表 |
| `schema` | string | | 要更新的数据库 schema \(默认public\)。用于访问其他 schema 下的表。|
| `filter` | string | 是 | PostgREST 筛选条件,用于定位要更新的行 \(例如:"id=eq.123"\) |
| `data` | object | 是 | 要在匹配行中更新的数据 |
| `table` | string | 是 | 要更新的 Supabase 表的名称 |
| `filter` | string | | 用于标识要更新行的 PostgREST 筛选条件 \(例如:"id=eq.123"\) |
| `data` | object | 是 | 要更新到匹配行的数据 |
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
#### 输出
@@ -135,9 +131,8 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要删除数据的 Supabase 表 |
| `schema` | string | | 要删除数据的数据库 schema \(默认public\)。用于访问其他 schema 下的表。|
| `filter` | string | 是 | PostgREST 筛选条件,用于定位要删除的行 \(例如:"id=eq.123"\) |
| `table` | string | 是 | 要删除的 Supabase 表的名称 |
| `filter` | string | | 用于标识要删除行的 PostgREST 筛选条件 \(例如:"id=eq.123"\) |
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
#### 输出
@@ -156,9 +151,8 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| 参数 | 类型 | 必需 | 描述 |
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要 upsert 数据的 Supabase 表名 |
| `schema` | string | | 要 upsert 的数据库 schema \(默认public\)。用于访问其他 schema 下的表。|
| `data` | array | 是 | 要 upsert插入或更新的数据——对象数组或单个对象 |
| `table` | string | 是 | 要插入或更新数据的 Supabase 表名 |
| `data` | array | | 要插入或更新的数据 \(插入或更新\) - 对象数组或单个对象 |
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
#### 输出
@@ -178,7 +172,6 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要统计行数的 Supabase 表名 |
| `schema` | string | 否 | 要统计的数据库 schema \(默认public\)。用于访问其他 schema 下的表。 |
| `filter` | string | 否 | PostgREST 过滤条件 \(例如:"status=eq.active"\) |
| `countType` | string | 否 | 计数类型exact、planned 或 estimated \(默认exact\) |
| `apiKey` | string | 是 | 您的 Supabase 服务角色密钥 |
@@ -200,9 +193,8 @@ Sim 的 Supabase 集成使您能够轻松地将代理工作流连接到您的 Su
| --------- | ---- | -------- | ----------- |
| `projectId` | string | 是 | 您的 Supabase 项目 ID \(例如jdrkgepadsdopsntdlom\) |
| `table` | string | 是 | 要搜索的 Supabase 表名 |
| `schema` | string | | 要搜索的数据库 schema \(默认public\)。用于访问其他 schema 下的表。 |
| `column` | string | 是 | 搜索的列名 |
| `query` | string | 是 | 搜索查询内容 |
| `column` | string | | 要搜索的 |
| `query` | string | 是 | 搜索查询 |
| `searchType` | string | 否 | 搜索类型plain、phrase 或 websearch \(默认websearch\) |
| `language` | string | 否 | 文本搜索配置的语言 \(默认english\) |
| `limit` | number | 否 | 返回的最大行数 |

View File

@@ -698,49 +698,49 @@ checksums:
content/11: 04bd9805ef6a50af8469463c34486dbf
content/12: a3671dd7ba76a87dc75464d9bf9b7b4b
content/13: 371d0e46b4bd2c23f559b8bc112f6955
content/14: 80578981b8b3a1cf579e52ff05e7468d
content/14: 319b69dde6e263f38497a0a84dc58e60
content/15: bcadfc362b69078beee0088e5936c98b
content/16: 09ed43219d02501c829594dbf4128959
content/17: 88ae2285d728c80937e1df8194d92c60
content/18: cb8a6d5bf54beed29f0809f80b27648d
content/19: 371d0e46b4bd2c23f559b8bc112f6955
content/20: 3212d5f414ea8ad920385eff8b18e61a
content/20: cdbe4726ca8dd4bb6a013055e14117f5
content/21: bcadfc362b69078beee0088e5936c98b
content/22: 5c59a9fe4d16c81655acd350d08a052e
content/23: 7d96d99e45880195ccbd34bddaac6319
content/24: 75d05f96dff406db06b338d9ab8d0bd7
content/25: 371d0e46b4bd2c23f559b8bc112f6955
content/26: cfd801fa517b4bcfa5fa034b2c4e908a
content/26: c5aa1613dc223a1c4348732b24c13e8b
content/27: bcadfc362b69078beee0088e5936c98b
content/28: a0284632eb0a15e66f69479ec477c5b1
content/29: b1e60734e590a8ad894a96581a253bf4
content/30: bebedc0826cdad098631d8090379501e
content/31: 371d0e46b4bd2c23f559b8bc112f6955
content/32: d1ac6e1bb29969a317cb9b98bbd3bf5e
content/32: 017a5d7255b0b029754d91c0c4063a15
content/33: bcadfc362b69078beee0088e5936c98b
content/34: 31a3025a3f0b9f7348f8c0b45a47d1dd
content/35: 9378daf3cd90dde934d19068f626e262
content/36: 65b3f733c34d0adb46e689c95980a45f
content/37: 371d0e46b4bd2c23f559b8bc112f6955
content/38: 2308dbb1aa8b94869e61aa991dc1182a
content/38: 35e18d7355312276b6706a79cdb4bb98
content/39: bcadfc362b69078beee0088e5936c98b
content/40: ebebd366813cd5cfb35e70121ab97565
content/41: 16f5fe78076e326d643c194312c730a5
content/42: 5d1098c4ada4a79ade1464bd8853fc9e
content/43: 371d0e46b4bd2c23f559b8bc112f6955
content/44: 39557f0370b8aa3677ee69977a78cb5d
content/44: b37f6692238f3a45bfcf106f0d192b21
content/45: bcadfc362b69078beee0088e5936c98b
content/46: 1788748095a805b62a0e21403789dad7
content/47: 0c504770bfe726a98df3e3fadeaf7640
content/48: 5673ae2a352e532a43b65322df0d33a8
content/49: 371d0e46b4bd2c23f559b8bc112f6955
content/50: f2e7eefa05af1e373a3c667164b00276
content/50: 479fb8fb1b760e9a045b375aabfb1a45
content/51: bcadfc362b69078beee0088e5936c98b
content/52: 72f3fd98f52ec42be8a95a019d74c253
content/53: 14db7775b92dc99c54d1fd5497d298ca
content/54: f84723b1195268ffc05970b701bf866a
content/55: 371d0e46b4bd2c23f559b8bc112f6955
content/56: 826a89366439187745611f2080d6ddd1
content/56: 90c244b243ef73e8d08cc6265ff61445
content/57: bcadfc362b69078beee0088e5936c98b
content/58: f1735da5af62123df5e5345ab834b2fa
content/59: dd231637d3d327f0daf546fe92594ac6
@@ -903,7 +903,7 @@ checksums:
content/24: 228a8ece96627883153b826a1cbaa06c
content/25: 53abe061a259c296c82676b4770ddd1b
content/26: 371d0e46b4bd2c23f559b8bc112f6955
content/27: 5b9546f77fbafc0741f3fc2548f81c7e
content/27: 03e8b10ec08b354de98e360b66b779e3
content/28: bcadfc362b69078beee0088e5936c98b
content/29: b82def7d82657f941fbe60df3924eeeb
content/30: 1ca7ee3856805fa1718031c5f75b6ffb
@@ -1896,25 +1896,25 @@ checksums:
content/5: b061763378e5f0aca9a25f819d03961d
content/6: 75972cfff5aa2f1d4c24f2a1c867cfb7
content/7: 371d0e46b4bd2c23f559b8bc112f6955
content/8: deb556a0ce8537461dd58a02e584d808
content/8: 8579c5fe58782fed019acfd5019c515e
content/9: bcadfc362b69078beee0088e5936c98b
content/10: 467bff9c1a90c96930d1b05286dd4bf8
content/11: ba06fa96a9fe3d308546a32490e5a8d8
content/12: 8461d1c991d4c595a64940dc51b130e5
content/12: 58490686b3358445d2fa89e8a048fb51
content/13: 371d0e46b4bd2c23f559b8bc112f6955
content/14: 72895fa081d39eec0bdf7877fd53580e
content/14: 9acf9c7ac7b83db796a143abc8f8de0f
content/15: bcadfc362b69078beee0088e5936c98b
content/16: 8a2c3d0b818f57012cb3d2e50d2dd05d
content/16: d4ac7483993edc4308e6034d4bd551bd
content/17: e13dff194d0bc1cecec833cb9805ceaa
content/18: 8813ba0bc9fbf636f3a38e59667df896
content/19: 371d0e46b4bd2c23f559b8bc112f6955
content/20: d71b6bb8e2dd6ce98101aec6a1dd77f2
content/21: bcadfc362b69078beee0088e5936c98b
content/22: 7b17554ad765c6283fc5fe6c29b9cc77
content/22: d2f04b0f593a08f7656e7431a6b4e5e5
content/23: 9eebc263273839cc24231b56fd90b71d
content/24: c442d573aaae976f5fab534388cee115
content/24: 9acb060c11b48ae498d55aceb053b996
content/25: 371d0e46b4bd2c23f559b8bc112f6955
content/26: 2bb55f4566a06bedea778f010f093d19
content/26: 707c54d664fcfc307cea6027721b940b
content/27: bcadfc362b69078beee0088e5936c98b
content/28: b48618ae66e09c34074972d091ceaef0
content/29: b3f310d5ef115bea5a8b75bf25d7ea9a
@@ -2521,9 +2521,9 @@ checksums:
content/22: ef92d95455e378abe4d27a1cdc5e1aed
content/23: febd6019055f3754953fd93395d0dbf2
content/24: 371d0e46b4bd2c23f559b8bc112f6955
content/25: caf6acbe2a4495ca055cb9006ce47250
content/25: 7ef3f388e5ee9346bac54c771d825f40
content/26: bcadfc362b69078beee0088e5936c98b
content/27: 57662dd91f8d1d807377fd48fa0e9142
content/27: e0fa91c45aa780fc03e91df77417f893
content/28: b463f54cd5fe2458b5842549fbb5e1ce
content/29: 55f8c724e1a2463bc29a32518a512c73
content/30: 371d0e46b4bd2c23f559b8bc112f6955
@@ -2638,14 +2638,8 @@ checksums:
content/139: 33fde4c3da4584b51f06183b7b192a78
content/140: bcadfc362b69078beee0088e5936c98b
content/141: b7451190f100388d999c183958d787a7
content/142: d0f9e799e2e5cc62de60668d35fd846f
content/143: b19069ff19899fe202217e06e002c447
content/144: 371d0e46b4bd2c23f559b8bc112f6955
content/145: 480fd62f8d9cc18467e82f4c3f70beea
content/146: bcadfc362b69078beee0088e5936c98b
content/147: 4e73a65d3b873f3979587e10a0f39e72
content/148: b3f310d5ef115bea5a8b75bf25d7ea9a
content/149: 4930918f803340baa861bed9cdf789de
content/142: b3f310d5ef115bea5a8b75bf25d7ea9a
content/143: 4930918f803340baa861bed9cdf789de
8f76e389f6226f608571622b015ca6a1:
meta/title: ddfe2191ea61b34d8b7cc1d7c19b94ac
meta/description: 049ff551f2ebabb15cdea0c71bd8e4eb
@@ -4407,29 +4401,26 @@ checksums:
content/9: 072b097db5f884122f323c5d8386671c
content/10: 58882acf2056c9e54e27f38f1203f0cd
content/11: 68d81199ca72ece1c31474f5729f98cd
content/12: 95cb1a1f36f0e562d85e0e54f0f439ba
content/13: ddaad9625d2ed0f4ec1b96094e7ec7c0
content/14: a050d81709025cb6c6c72619c80219c5
content/15: 8a7329562f5f1324f94ae0bc9b2b3853
content/16: bf918d2d9b62a9ab640371cea0629c28
content/17: 9b2ebe8a9c3efa9f14c35f6d71a7a274
content/18: fcc7c89bc4c545daecba16f986165f9e
content/19: 395dc70853e0c9ca156db72f3156ae36
content/20: b5b3cc0a48c0f06fa4c475544be4a0e6
content/21: 462ac511233020ce80369b6f834387bf
content/22: cad571a6ce02e0807c5c23699b115c17
content/23: 50251a58c6c1b448dc9a9ed65d7db7aa
content/24: 791148e6bad524eb53286aeea5ddbf24
content/25: ec70f2b34ad4b3839ff42a76c0378655
content/26: dd4d559eba1a9c83a9166bc491d2b2ac
content/27: 6b0f081f8453c427f3b3720d44e62857
content/28: 84dc89f2666e7201c72c258cb4c4e2d0
content/29: 4ae642d59321118a88ebf4ce8751c05a
content/30: 4703a7028a16d9e716631be2a49c72bb
content/31: d1c7c19aae4736f403b0df60cb6e48a4
content/32: 9e5a786192608844493dfbb6e4100886
content/33: 8961e5fb3a49bb48580b23dfd5e053c6
content/34: 1022391b9b79b1d2e9f8db789f9c50a2
content/12: d152f1dcd6cd068be14a7ae05afd216e
content/13: bf918d2d9b62a9ab640371cea0629c28
content/14: 9b2ebe8a9c3efa9f14c35f6d71a7a274
content/15: fcc7c89bc4c545daecba16f986165f9e
content/16: 395dc70853e0c9ca156db72f3156ae36
content/17: b5b3cc0a48c0f06fa4c475544be4a0e6
content/18: 462ac511233020ce80369b6f834387bf
content/19: cad571a6ce02e0807c5c23699b115c17
content/20: 50251a58c6c1b448dc9a9ed65d7db7aa
content/21: 791148e6bad524eb53286aeea5ddbf24
content/22: ec70f2b34ad4b3839ff42a76c0378655
content/23: dd4d559eba1a9c83a9166bc491d2b2ac
content/24: 6b0f081f8453c427f3b3720d44e62857
content/25: 84dc89f2666e7201c72c258cb4c4e2d0
content/26: 4ae642d59321118a88ebf4ce8751c05a
content/27: 4703a7028a16d9e716631be2a49c72bb
content/28: d1c7c19aae4736f403b0df60cb6e48a4
content/29: 9e5a786192608844493dfbb6e4100886
content/30: 8961e5fb3a49bb48580b23dfd5e053c6
content/31: 1022391b9b79b1d2e9f8db789f9c50a2
3fd794279590b9d143d409252c8bcf91:
meta/title: 439cb79e8dfd7923d35b85cfbb6fd201
content/0: dc697d1a69b3cb0152fda60b44bc7da1
@@ -4570,11 +4561,11 @@ checksums:
content/10: d19c8c67f52eb08b6a49c0969a9c8b86
content/11: 4024a36e0d9479ff3191fb9cd2b2e365
content/12: 0396a1e5d9548207f56e6b6cae85a542
content/13: 4bfdeac5ad21c75209dcdfde85aa52b0
content/14: 35df9a16b866dbe4bb9fc1d7aee42711
content/15: 135c044066cea8cc0e22f06d67754ec5
content/16: 6882b91e30548d7d331388c26cf2e948
content/17: 29aed7061148ae46fa6ec8bcbc857c3d
content/13: e3e263fb516c8a5413e94064e7700410
content/14: 41bbe0664b778cddce4e14055f6342a9
content/15: 8a274099c74e6b8dac89b8c413601d98
content/16: 55cac8cbd4373a3d1b51c8bbc536a7ce
content/17: 8159087a0aa1e5967545fa6ce86ec5f4
content/18: e0571c88ea5bcd4305a6f5772dcbed98
content/19: 83fc31418ff454a5e06b290e3708ef32
content/20: 4392b5939a6d5774fb080cad1ee1dbb8
@@ -4597,7 +4588,7 @@ checksums:
content/37: 7bb928aba33a4013ad5f08487da5bbf9
content/38: dbbf313837f13ddfa4a8843d71cb9cc4
content/39: cf10560ae6defb8ee5da344fc6509f6e
content/40: 1dea5c6442c127ae290185db0cef067b
content/40: c5dc6e5de6e45b17ee1f5eb567a18e2f
content/41: 332dab0588fb35dabb64b674ba6120eb
content/42: 714b3f99b0a8686bbb3434deb1f682b3
content/43: ba18ac99184b17d7e49bd1abdc814437
@@ -47160,7 +47151,7 @@ checksums:
content/9: b037aed60eb91ac0b010b6c1ce1a1a70
content/10: b1c4181c4bc75edd5dfa188bcdd3b6c4
content/11: 371d0e46b4bd2c23f559b8bc112f6955
content/12: a71a30e9f91c10daf481ea8f542e91f6
content/12: a830a7e59569945973febaed7634fcf8
content/13: bcadfc362b69078beee0088e5936c98b
content/14: 59c08999f9c404330ebd8f8a7d21e1a1
content/15: 49d191d312481589419c68a5506b0d71
@@ -47172,7 +47163,7 @@ checksums:
content/21: 2e70c0a22a98675a13b493b9761ff92f
content/22: 107f6e51a1e896ee4d18f8ed4f82c50f
content/23: 371d0e46b4bd2c23f559b8bc112f6955
content/24: e506fbf4b80deecb3b44b29b8dc3438b
content/24: 3a9d07c3ebf40ef00a67fb694dfbcf97
content/25: bcadfc362b69078beee0088e5936c98b
content/26: a9096a341b00ce4f4891daaca2586d1c
content/27: 934a0124aa2118682b2b17fa258ff06a
@@ -47184,7 +47175,7 @@ checksums:
content/33: 1a1e332b525e86f7fd92f9da1ac0096c
content/34: 00098e1591c0f80ef6287d934d391409
content/35: 371d0e46b4bd2c23f559b8bc112f6955
content/36: e52688ff2fa61ce71026f33930e1ec86
content/36: 991ec136b689e0dc95719260a0cef14a
content/37: bcadfc362b69078beee0088e5936c98b
content/38: d84fb23e5dfc9d41a177acd7dfb28e72
content/39: 17be090a79154f557bc96f940c687aea
@@ -47196,7 +47187,7 @@ checksums:
content/45: c76943404f9c8d34a85e6315359ed0c4
content/46: b5e111e430aa1c929fb07d5844bf65eb
content/47: 371d0e46b4bd2c23f559b8bc112f6955
content/48: 6692edffddc28d3c64974ded23d1def2
content/48: 3e3ced1f6eb6c0ef39098531beb12598
content/49: bcadfc362b69078beee0088e5936c98b
content/50: dbc08cce26f9565e719891bbbf4632a9
content/51: d0ce65f5420745c45ab42b7edd135bf4
@@ -47208,37 +47199,37 @@ checksums:
content/57: 440f2732ad006bee8cccc975fdbf673a
content/58: 7a7048c54763b0109643f37e583381ce
content/59: 371d0e46b4bd2c23f559b8bc112f6955
content/60: 11ad0a529a7fcc5892ae811cde6894f6
content/60: 5672a02f72bdb165f91b43e4ad24c4a9
content/61: bcadfc362b69078beee0088e5936c98b
content/62: c7055d8ce044e49929d4f005a28d7c0a
content/63: 2d7bad4340c1bc6a28e836e180e26c00
content/64: 576dbecf29644e7abf59d25ffda5728c
content/65: 371d0e46b4bd2c23f559b8bc112f6955
content/66: 59015900ce6b64caff0784491ec59ff9
content/66: 61a490d594e10484da99d6b0e8ee684d
content/67: bcadfc362b69078beee0088e5936c98b
content/68: 2f225a893086726db6b6a994cc8a5e3c
content/69: 63cbf703cf33e0fee06f12fb23184352
content/70: dae1fda5ec57e1b598a7e2596007a775
content/71: 371d0e46b4bd2c23f559b8bc112f6955
content/72: 757f42df5247f2e6684ab32888d30e11
content/72: 7f00464f8e9368368ed1104fba516e5d
content/73: bcadfc362b69078beee0088e5936c98b
content/74: 380f805a5118dd4957f4fcce41e01b86
content/75: 935f1a713d05f32d3d826434a7e715ee
content/76: e505d8f656fb6e3b65a98cb73d744598
content/77: 371d0e46b4bd2c23f559b8bc112f6955
content/78: 2e77859b0f2c89186fc6a2d51287ea47
content/78: e218c4f319e6a6a50c0535d3ee5e8fcf
content/79: bcadfc362b69078beee0088e5936c98b
content/80: 22bd99d5b844817b808b9d0d3baddac4
content/81: e959b48af94a559e9c46cbd7653d2dd2
content/82: 5e3c04c5a9fabfceb7fcc00215f93bf9
content/83: 371d0e46b4bd2c23f559b8bc112f6955
content/84: a92b2a22061ee6fd453af32e0155f5aa
content/84: 8382a2ecb67d09244a2f004e098e0b46
content/85: bcadfc362b69078beee0088e5936c98b
content/86: d84fb23e5dfc9d41a177acd7dfb28e72
content/87: c886f11a0852010b90a1032b97118920
content/88: c60c832c08f9e1ff5f91565bf4ba549e
content/89: 371d0e46b4bd2c23f559b8bc112f6955
content/90: 1545794f4e8e696db96c3b660de684ec
content/90: 9019e9ff616caa381458fe1526c87840
content/91: bcadfc362b69078beee0088e5936c98b
content/92: 573530e346d195727862b03b380f40fc
content/93: 3d31dedf076ec23547189a3eb5fe04c4
@@ -47250,7 +47241,7 @@ checksums:
content/99: e1a03f917ad8b0a1ebec9a601aa3eede
content/100: 3aa857b8f85da07ee2d87e65c95b76d0
content/101: 371d0e46b4bd2c23f559b8bc112f6955
content/102: cc49a24c087d08717866a162cc47776c
content/102: c9e677ff65fd547dffa46b9c517402ac
content/103: bcadfc362b69078beee0088e5936c98b
content/104: c6d621ee3cdc66de2c20b70a39aafe12
content/105: b3f310d5ef115bea5a8b75bf25d7ea9a

View File

@@ -573,10 +573,10 @@ export default function LoginPage({
<Dialog open={forgotPasswordOpen} onOpenChange={setForgotPasswordOpen}>
<DialogContent className='auth-card auth-card-shadow max-w-[540px] rounded-[10px] border backdrop-blur-sm'>
<DialogHeader>
<DialogTitle className='font-semibold text-black text-xl tracking-tight'>
<DialogTitle className='auth-text-primary font-semibold text-xl tracking-tight'>
Reset Password
</DialogTitle>
<DialogDescription className='text-muted-foreground text-sm'>
<DialogDescription className='auth-text-secondary text-sm'>
Enter your email address and we'll send you a link to reset your password if your
account exists.
</DialogDescription>

View File

@@ -109,7 +109,7 @@ export default function Footer({ fullWidth = false }: FooterProps) {
{FOOTER_BLOCKS.map((block) => (
<Link
key={block}
href={`https://docs.sim.ai/blocks/${block.toLowerCase().replaceAll(' ', '-')}`}
href={`https://docs.sim.ai/blocks/${block.toLowerCase().replace(' ', '-')}`}
target='_blank'
rel='noopener noreferrer'
className='text-[14px] text-muted-foreground transition-colors hover:text-foreground'

View File

@@ -41,7 +41,7 @@ interface PricingTier {
* Free plan features with consistent icons
*/
const FREE_PLAN_FEATURES: PricingFeature[] = [
{ icon: DollarSign, text: '$20 usage limit' },
{ icon: DollarSign, text: '$10 usage limit' },
{ icon: HardDrive, text: '5GB file storage' },
{ icon: Workflow, text: 'Public template access' },
{ icon: Database, text: 'Limited log retention' },

View File

@@ -1,7 +1,8 @@
import Image from 'next/image'
import Link from 'next/link'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { getAllPostMeta } from '@/lib/blog/registry'
import { soehne } from '@/app/_styles/fonts/soehne/soehne'
import { PostGrid } from '@/app/(landing)/studio/post-grid'
export const revalidate = 3600
@@ -17,6 +18,7 @@ export default async function StudioIndex({
const all = await getAllPostMeta()
const filtered = tag ? all.filter((p) => p.tags.includes(tag)) : all
// Sort to ensure featured post is first on page 1
const sorted =
pageNum === 1
? filtered.sort((a, b) => {
@@ -61,7 +63,69 @@ export default async function StudioIndex({
</div> */}
{/* Grid layout for consistent rows */}
<PostGrid posts={posts} />
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3'>
{posts.map((p, i) => {
return (
<Link key={p.slug} href={`/studio/${p.slug}`} className='group flex flex-col'>
<div className='flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 transition-colors duration-300 hover:border-gray-300'>
<Image
src={p.ogImage}
alt={p.title}
width={800}
height={450}
className='h-48 w-full object-cover'
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
loading='lazy'
unoptimized
/>
<div className='flex flex-1 flex-col p-4'>
<div className='mb-2 text-gray-600 text-xs'>
{new Date(p.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</div>
<h3 className='shine-text mb-1 font-medium text-lg leading-tight'>{p.title}</h3>
<p className='mb-3 line-clamp-3 flex-1 text-gray-700 text-sm'>{p.description}</p>
<div className='flex items-center gap-2'>
<div className='-space-x-1.5 flex'>
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
.slice(0, 3)
.map((author, idx) => (
<Avatar key={idx} className='size-4 border border-white'>
<AvatarImage src={author?.avatarUrl} alt={author?.name} />
<AvatarFallback className='border border-white bg-gray-100 text-[10px] text-gray-600'>
{author?.name.slice(0, 2)}
</AvatarFallback>
</Avatar>
))}
</div>
<span className='text-gray-600 text-xs'>
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
.slice(0, 2)
.map((a) => a?.name)
.join(', ')}
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length > 2 && (
<>
{' '}
and{' '}
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2}{' '}
other
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2 >
1
? 's'
: ''}
</>
)}
</span>
</div>
</div>
</div>
</Link>
)
})}
</div>
{totalPages > 1 && (
<div className='mt-10 flex items-center justify-center gap-3'>

View File

@@ -1,90 +0,0 @@
'use client'
import Image from 'next/image'
import Link from 'next/link'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
interface Author {
id: string
name: string
avatarUrl?: string
url?: string
}
interface Post {
slug: string
title: string
description: string
date: string
ogImage: string
author: Author
authors?: Author[]
featured?: boolean
}
export function PostGrid({ posts }: { posts: Post[] }) {
return (
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3'>
{posts.map((p, index) => (
<Link key={p.slug} href={`/studio/${p.slug}`} className='group flex flex-col'>
<div className='flex h-full flex-col overflow-hidden rounded-xl border border-gray-200 transition-colors duration-300 hover:border-gray-300'>
{/* Image container with fixed aspect ratio to prevent layout shift */}
<div className='relative aspect-video w-full overflow-hidden'>
<Image
src={p.ogImage}
alt={p.title}
sizes='(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 33vw'
unoptimized
priority={index < 6}
loading={index < 6 ? undefined : 'lazy'}
fill
style={{ objectFit: 'cover' }}
/>
</div>
<div className='flex flex-1 flex-col p-4'>
<div className='mb-2 text-gray-600 text-xs'>
{new Date(p.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}
</div>
<h3 className='shine-text mb-1 font-medium text-lg leading-tight'>{p.title}</h3>
<p className='mb-3 line-clamp-3 flex-1 text-gray-700 text-sm'>{p.description}</p>
<div className='flex items-center gap-2'>
<div className='-space-x-1.5 flex'>
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
.slice(0, 3)
.map((author, idx) => (
<Avatar key={idx} className='size-4 border border-white'>
<AvatarImage src={author?.avatarUrl} alt={author?.name} />
<AvatarFallback className='border border-white bg-gray-100 text-[10px] text-gray-600'>
{author?.name.slice(0, 2)}
</AvatarFallback>
</Avatar>
))}
</div>
<span className='text-gray-600 text-xs'>
{(p.authors && p.authors.length > 0 ? p.authors : [p.author])
.slice(0, 2)
.map((a) => a?.name)
.join(', ')}
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length > 2 && (
<>
{' '}
and {(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2}{' '}
other
{(p.authors && p.authors.length > 0 ? p.authors : [p.author]).length - 2 > 1
? 's'
: ''}
</>
)}
</span>
</div>
</div>
</div>
</Link>
))}
</div>
)
}

View File

@@ -12,7 +12,6 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
pathname === '/' ||
pathname.startsWith('/login') ||
pathname.startsWith('/signup') ||
pathname.startsWith('/reset-password') ||
pathname.startsWith('/sso') ||
pathname.startsWith('/terms') ||
pathname.startsWith('/privacy') ||

View File

@@ -759,24 +759,3 @@ input[type="search"]::-ms-clear {
--surface-elevated: #202020;
}
}
/**
* Remove backticks from inline code in prose (Tailwind Typography default)
*/
.prose code::before,
.prose code::after {
content: none !important;
}
/**
* Remove underlines from heading anchor links in prose
*/
.prose h1 a,
.prose h2 a,
.prose h3 a,
.prose h4 a,
.prose h5 a,
.prose h6 a {
text-decoration: none !important;
color: inherit !important;
}

View File

@@ -32,17 +32,7 @@ export async function GET(request: NextRequest) {
.from(account)
.where(and(...whereConditions))
// Use the user's email as the display name (consistent with credential selector)
const userEmail = session.user.email
const accountsWithDisplayName = accounts.map((acc) => ({
id: acc.id,
accountId: acc.accountId,
providerId: acc.providerId,
displayName: userEmail || acc.providerId,
}))
return NextResponse.json({ accounts: accountsWithDisplayName })
return NextResponse.json({ accounts })
} catch (error) {
logger.error('Failed to fetch accounts', { error })
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })

View File

@@ -6,10 +6,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createMockRequest, setupAuthApiMocks } from '@/app/api/__test-utils__/utils'
vi.mock('@/lib/core/utils/urls', () => ({
getBaseUrl: vi.fn(() => 'https://app.example.com'),
}))
describe('Forget Password API Route', () => {
beforeEach(() => {
vi.resetModules()
@@ -19,7 +15,7 @@ describe('Forget Password API Route', () => {
vi.clearAllMocks()
})
it('should send password reset email successfully with same-origin redirectTo', async () => {
it('should send password reset email successfully', async () => {
setupAuthApiMocks({
operations: {
forgetPassword: { success: true },
@@ -28,7 +24,7 @@ describe('Forget Password API Route', () => {
const req = createMockRequest('POST', {
email: 'test@example.com',
redirectTo: 'https://app.example.com/reset',
redirectTo: 'https://example.com/reset',
})
const { POST } = await import('@/app/api/auth/forget-password/route')
@@ -43,36 +39,12 @@ describe('Forget Password API Route', () => {
expect(auth.auth.api.forgetPassword).toHaveBeenCalledWith({
body: {
email: 'test@example.com',
redirectTo: 'https://app.example.com/reset',
redirectTo: 'https://example.com/reset',
},
method: 'POST',
})
})
it('should reject external redirectTo URL', async () => {
setupAuthApiMocks({
operations: {
forgetPassword: { success: true },
},
})
const req = createMockRequest('POST', {
email: 'test@example.com',
redirectTo: 'https://evil.com/phishing',
})
const { POST } = await import('@/app/api/auth/forget-password/route')
const response = await POST(req)
const data = await response.json()
expect(response.status).toBe(400)
expect(data.message).toBe('Redirect URL must be a valid same-origin URL')
const auth = await import('@/lib/auth')
expect(auth.auth.api.forgetPassword).not.toHaveBeenCalled()
})
it('should send password reset email without redirectTo', async () => {
setupAuthApiMocks({
operations: {

View File

@@ -1,7 +1,6 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { auth } from '@/lib/auth'
import { isSameOrigin } from '@/lib/core/utils/validation'
import { createLogger } from '@/lib/logs/console/logger'
export const dynamic = 'force-dynamic'
@@ -14,15 +13,10 @@ const forgetPasswordSchema = z.object({
.email('Please provide a valid email address'),
redirectTo: z
.string()
.url('Redirect URL must be a valid URL')
.optional()
.or(z.literal(''))
.transform((val) => (val === '' || val === undefined ? undefined : val))
.refine(
(val) => val === undefined || (z.string().url().safeParse(val).success && isSameOrigin(val)),
{
message: 'Redirect URL must be a valid same-origin URL',
}
),
.transform((val) => (val === '' ? undefined : val)),
})
export async function POST(request: NextRequest) {

View File

@@ -2,7 +2,6 @@ import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { auth } from '@/lib/auth'
import { env } from '@/lib/core/config/env'
import { REDACTED_MARKER } from '@/lib/core/security/redaction'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('SSO-Register')
@@ -237,13 +236,13 @@ export async function POST(request: NextRequest) {
oidcConfig: providerConfig.oidcConfig
? {
...providerConfig.oidcConfig,
clientSecret: REDACTED_MARKER,
clientSecret: '[REDACTED]',
}
: undefined,
samlConfig: providerConfig.samlConfig
? {
...providerConfig.samlConfig,
cert: REDACTED_MARKER,
cert: '[REDACTED]',
}
: undefined,
},

View File

@@ -3,7 +3,6 @@ import { userStats } from '@sim/db/schema'
import { eq, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { logModelUsage } from '@/lib/billing/core/usage-log'
import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
import { checkInternalApiKey } from '@/lib/copilot/utils'
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
@@ -15,9 +14,6 @@ const logger = createLogger('BillingUpdateCostAPI')
const UpdateCostSchema = z.object({
userId: z.string().min(1, 'User ID is required'),
cost: z.number().min(0, 'Cost must be a non-negative number'),
model: z.string().min(1, 'Model is required'),
inputTokens: z.number().min(0).default(0),
outputTokens: z.number().min(0).default(0),
})
/**
@@ -75,12 +71,11 @@ export async function POST(req: NextRequest) {
)
}
const { userId, cost, model, inputTokens, outputTokens } = validation.data
const { userId, cost } = validation.data
logger.info(`[${requestId}] Processing cost update`, {
userId,
cost,
model,
})
// Check if user stats record exists (same as ExecutionLogger)
@@ -112,16 +107,6 @@ export async function POST(req: NextRequest) {
addedCost: cost,
})
// Log usage for complete audit trail
await logModelUsage({
userId,
source: 'copilot',
model,
inputTokens,
outputTokens,
cost,
})
// Check if user has hit overage threshold and bill incrementally
await checkAndBillOverageThreshold(userId)

View File

@@ -70,6 +70,19 @@ vi.mock('@/lib/core/utils/request', () => ({
generateRequestId: vi.fn().mockReturnValue('test-request-id'),
}))
vi.mock('@/app/api/workflows/[id]/execute/route', () => ({
createFilteredResult: vi.fn().mockImplementation((result: any) => ({
...result,
logs: undefined,
metadata: result.metadata
? {
...result.metadata,
workflowConnections: undefined,
}
: undefined,
})),
}))
describe('Chat Identifier API Route', () => {
const mockAddCorsHeaders = vi.fn().mockImplementation((response) => response)
const mockValidateChatAuth = vi.fn().mockResolvedValue({ authorized: true })

View File

@@ -1,6 +1,6 @@
import { randomUUID } from 'crypto'
import { db } from '@sim/db'
import { chat, workflow } from '@sim/db/schema'
import { chat } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
@@ -94,21 +94,6 @@ export async function POST(
if (!deployment.isActive) {
logger.warn(`[${requestId}] Chat is not active: ${identifier}`)
const [workflowRecord] = await db
.select({ workspaceId: workflow.workspaceId })
.from(workflow)
.where(eq(workflow.id, deployment.workflowId))
.limit(1)
const workspaceId = workflowRecord?.workspaceId
if (!workspaceId) {
logger.warn(`[${requestId}] Cannot log: workflow ${deployment.workflowId} has no workspace`)
return addCorsHeaders(
createErrorResponse('This chat is currently unavailable', 403),
request
)
}
const executionId = randomUUID()
const loggingSession = new LoggingSession(
deployment.workflowId,
@@ -119,7 +104,7 @@ export async function POST(
await loggingSession.safeStart({
userId: deployment.userId,
workspaceId,
workspaceId: '', // Will be resolved if needed
variables: {},
})
@@ -184,14 +169,7 @@ export async function POST(
const { actorUserId, workflowRecord } = preprocessResult
const workspaceOwnerId = actorUserId!
const workspaceId = workflowRecord?.workspaceId
if (!workspaceId) {
logger.error(`[${requestId}] Workflow ${deployment.workflowId} has no workspaceId`)
return addCorsHeaders(
createErrorResponse('Workflow has no associated workspace', 500),
request
)
}
const workspaceId = workflowRecord?.workspaceId || ''
try {
const selectedOutputs: string[] = []
@@ -206,6 +184,7 @@ export async function POST(
const { createStreamingResponse } = await import('@/lib/workflows/streaming/streaming')
const { SSE_HEADERS } = await import('@/lib/core/utils/sse')
const { createFilteredResult } = await import('@/app/api/workflows/[id]/execute/route')
const workflowInput: any = { input, conversationId }
if (files && Array.isArray(files) && files.length > 0) {
@@ -266,6 +245,7 @@ export async function POST(
isSecureMode: true,
workflowTriggerType: 'chat',
},
createFilteredResult,
executionId,
})

View File

@@ -11,7 +11,7 @@ import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('CopilotChatsListAPI')
export async function GET(_request: NextRequest) {
export async function GET(_req: NextRequest) {
try {
const { userId, isAuthenticated } = await authenticateCopilotRequestSessionOnly()
if (!isAuthenticated || !userId) {

View File

@@ -38,13 +38,14 @@ export async function GET(
const cloudKey = isCloudPath ? path.slice(1).join('/') : fullPath
const contextParam = request.nextUrl.searchParams.get('context')
const legacyBucketType = request.nextUrl.searchParams.get('bucket')
const context = contextParam || (isCloudPath ? inferContextFromKey(cloudKey) : undefined)
if (context === 'profile-pictures' || context === 'og-images') {
logger.info(`Serving public ${context}:`, { cloudKey })
if (context === 'profile-pictures') {
logger.info('Serving public profile picture:', { cloudKey })
if (isUsingCloudStorage() || isCloudPath) {
return await handleCloudProxyPublic(cloudKey, context)
return await handleCloudProxyPublic(cloudKey, context, legacyBucketType)
}
return await handleLocalFilePublic(fullPath)
}
@@ -181,7 +182,8 @@ async function handleCloudProxy(
async function handleCloudProxyPublic(
cloudKey: string,
context: StorageContext
context: StorageContext,
legacyBucketType?: string | null
): Promise<NextResponse> {
try {
let fileBuffer: Buffer

View File

@@ -141,23 +141,6 @@ export async function DELETE(
)
}
// Check if deleting this folder would delete the last workflow(s) in the workspace
const workflowsInFolder = await countWorkflowsInFolderRecursively(
id,
existingFolder.workspaceId
)
const totalWorkflowsInWorkspace = await db
.select({ id: workflow.id })
.from(workflow)
.where(eq(workflow.workspaceId, existingFolder.workspaceId))
if (workflowsInFolder > 0 && workflowsInFolder >= totalWorkflowsInWorkspace.length) {
return NextResponse.json(
{ error: 'Cannot delete folder containing the only workflow(s) in the workspace' },
{ status: 400 }
)
}
// Recursively delete folder and all its contents
const deletionStats = await deleteFolderRecursively(id, existingFolder.workspaceId)
@@ -219,34 +202,6 @@ async function deleteFolderRecursively(
return stats
}
/**
* Counts the number of workflows in a folder and all its subfolders recursively.
*/
async function countWorkflowsInFolderRecursively(
folderId: string,
workspaceId: string
): Promise<number> {
let count = 0
const workflowsInFolder = await db
.select({ id: workflow.id })
.from(workflow)
.where(and(eq(workflow.folderId, folderId), eq(workflow.workspaceId, workspaceId)))
count += workflowsInFolder.length
const childFolders = await db
.select({ id: workflowFolder.id })
.from(workflowFolder)
.where(and(eq(workflowFolder.parentId, folderId), eq(workflowFolder.workspaceId, workspaceId)))
for (const childFolder of childFolders) {
count += await countWorkflowsInFolderRecursively(childFolder.id, workspaceId)
}
return count
}
// Helper function to check for circular references
async function checkForCircularReference(folderId: string, parentId: string): Promise<boolean> {
let currentParentId: string | null = parentId

View File

@@ -1,6 +1,7 @@
import { runs } from '@trigger.dev/sdk'
import { type NextRequest, NextResponse } from 'next/server'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { authenticateApiKeyFromHeader, updateApiKeyLastUsed } from '@/lib/api-key/service'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { createErrorResponse } from '@/app/api/workflows/utils'
@@ -17,44 +18,38 @@ export async function GET(
try {
logger.debug(`[${requestId}] Getting status for task: ${taskId}`)
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized task status request`)
return createErrorResponse(authResult.error || 'Authentication required', 401)
// Try session auth first (for web UI)
const session = await getSession()
let authenticatedUserId: string | null = session?.user?.id || null
if (!authenticatedUserId) {
const apiKeyHeader = request.headers.get('x-api-key')
if (apiKeyHeader) {
const authResult = await authenticateApiKeyFromHeader(apiKeyHeader)
if (authResult.success && authResult.userId) {
authenticatedUserId = authResult.userId
if (authResult.keyId) {
await updateApiKeyLastUsed(authResult.keyId).catch((error) => {
logger.warn(`[${requestId}] Failed to update API key last used timestamp:`, {
keyId: authResult.keyId,
error,
})
})
}
}
}
}
const authenticatedUserId = authResult.userId
if (!authenticatedUserId) {
return createErrorResponse('Authentication required', 401)
}
// Fetch task status from Trigger.dev
const run = await runs.retrieve(taskId)
logger.debug(`[${requestId}] Task ${taskId} status: ${run.status}`)
const payload = run.payload as any
if (payload?.workflowId) {
const { verifyWorkflowAccess } = await import('@/socket-server/middleware/permissions')
const accessCheck = await verifyWorkflowAccess(authenticatedUserId, payload.workflowId)
if (!accessCheck.hasAccess) {
logger.warn(`[${requestId}] User ${authenticatedUserId} denied access to task ${taskId}`, {
workflowId: payload.workflowId,
})
return createErrorResponse('Access denied', 403)
}
logger.debug(`[${requestId}] User ${authenticatedUserId} has access to task ${taskId}`)
} else {
if (payload?.userId && payload.userId !== authenticatedUserId) {
logger.warn(
`[${requestId}] User ${authenticatedUserId} attempted to access task ${taskId} owned by ${payload.userId}`
)
return createErrorResponse('Access denied', 403)
}
if (!payload?.userId) {
logger.warn(
`[${requestId}] Task ${taskId} has no ownership information in payload. Denying access for security.`
)
return createErrorResponse('Access denied', 403)
}
}
// Map Trigger.dev status to our format
const statusMap = {
QUEUED: 'queued',
WAITING_FOR_DEPLOY: 'queued',
@@ -72,6 +67,7 @@ export async function GET(
const mappedStatus = statusMap[run.status as keyof typeof statusMap] || 'unknown'
// Build response based on status
const response: any = {
success: true,
taskId,
@@ -81,18 +77,21 @@ export async function GET(
},
}
// Add completion details if finished
if (mappedStatus === 'completed') {
response.output = run.output // This contains the workflow execution results
response.metadata.completedAt = run.finishedAt
response.metadata.duration = run.durationMs
}
// Add error details if failed
if (mappedStatus === 'failed') {
response.error = run.error
response.metadata.completedAt = run.finishedAt
response.metadata.duration = run.durationMs
}
// Add progress info if still processing
if (mappedStatus === 'processing' || mappedStatus === 'queued') {
response.estimatedDuration = 180000 // 3 minutes max from our config
}
@@ -108,3 +107,6 @@ export async function GET(
return createErrorResponse('Failed to fetch task status', 500)
}
}
// TODO: Implement task cancellation via Trigger.dev API if needed
// export async function DELETE() { ... }

View File

@@ -156,7 +156,6 @@ export async function POST(
const validatedData = CreateChunkSchema.parse(searchParams)
const docTags = {
// Text tags (7 slots)
tag1: doc.tag1 ?? null,
tag2: doc.tag2 ?? null,
tag3: doc.tag3 ?? null,
@@ -164,19 +163,6 @@ export async function POST(
tag5: doc.tag5 ?? null,
tag6: doc.tag6 ?? null,
tag7: doc.tag7 ?? null,
// Number tags (5 slots)
number1: doc.number1 ?? null,
number2: doc.number2 ?? null,
number3: doc.number3 ?? null,
number4: doc.number4 ?? null,
number5: doc.number5 ?? null,
// Date tags (2 slots)
date1: doc.date1 ?? null,
date2: doc.date2 ?? null,
// Boolean tags (3 slots)
boolean1: doc.boolean1 ?? null,
boolean2: doc.boolean2 ?? null,
boolean3: doc.boolean3 ?? null,
}
const newChunk = await createChunk(

View File

@@ -72,16 +72,6 @@ describe('Document By ID API Route', () => {
tag5: null,
tag6: null,
tag7: null,
number1: null,
number2: null,
number3: null,
number4: null,
number5: null,
date1: null,
date2: null,
boolean1: null,
boolean2: null,
boolean3: null,
deletedAt: null,
}

View File

@@ -23,7 +23,7 @@ const UpdateDocumentSchema = z.object({
processingError: z.string().optional(),
markFailedDueToTimeout: z.boolean().optional(),
retryProcessing: z.boolean().optional(),
// Text tag fields
// Tag fields
tag1: z.string().optional(),
tag2: z.string().optional(),
tag3: z.string().optional(),
@@ -31,19 +31,6 @@ const UpdateDocumentSchema = z.object({
tag5: z.string().optional(),
tag6: z.string().optional(),
tag7: z.string().optional(),
// Number tag fields
number1: z.string().optional(),
number2: z.string().optional(),
number3: z.string().optional(),
number4: z.string().optional(),
number5: z.string().optional(),
// Date tag fields
date1: z.string().optional(),
date2: z.string().optional(),
// Boolean tag fields
boolean1: z.string().optional(),
boolean2: z.string().optional(),
boolean3: z.string().optional(),
})
export async function GET(

View File

@@ -80,16 +80,6 @@ describe('Knowledge Base Documents API Route', () => {
tag5: null,
tag6: null,
tag7: null,
number1: null,
number2: null,
number3: null,
number4: null,
number5: null,
date1: null,
date2: null,
boolean1: null,
boolean2: null,
boolean3: null,
deletedAt: null,
}

View File

@@ -34,24 +34,13 @@ const CreateDocumentSchema = z.object({
documentTagsData: z.string().optional(),
})
/**
* Schema for bulk document creation with processing options
*
* Processing options units:
* - chunkSize: tokens (1 token ≈ 4 characters)
* - minCharactersPerChunk: characters
* - chunkOverlap: characters
*/
const BulkCreateDocumentsSchema = z.object({
documents: z.array(CreateDocumentSchema),
processingOptions: z.object({
/** Maximum chunk size in tokens (1 token ≈ 4 characters) */
chunkSize: z.number().min(100).max(4000),
/** Minimum chunk size in characters */
minCharactersPerChunk: z.number().min(1).max(2000),
recipe: z.string(),
lang: z.string(),
/** Overlap between chunks in characters */
chunkOverlap: z.number().min(0).max(500),
}),
bulk: z.literal(true),

View File

@@ -12,14 +12,6 @@ import { checkKnowledgeBaseAccess, checkKnowledgeBaseWriteAccess } from '@/app/a
const logger = createLogger('KnowledgeBaseByIdAPI')
/**
* Schema for updating a knowledge base
*
* Chunking config units:
* - maxSize: tokens (1 token ≈ 4 characters)
* - minSize: characters
* - overlap: tokens (1 token ≈ 4 characters)
*/
const UpdateKnowledgeBaseSchema = z.object({
name: z.string().min(1, 'Name is required').optional(),
description: z.string().optional(),
@@ -28,27 +20,14 @@ const UpdateKnowledgeBaseSchema = z.object({
workspaceId: z.string().nullable().optional(),
chunkingConfig: z
.object({
/** Maximum chunk size in tokens (1 token ≈ 4 characters) */
maxSize: z.number().min(100).max(4000),
/** Minimum chunk size in characters */
minSize: z.number().min(1).max(2000),
/** Overlap between chunks in characters */
overlap: z.number().min(0).max(500),
maxSize: z.number(),
minSize: z.number(),
overlap: z.number(),
})
.refine(
(data) => {
// Convert maxSize from tokens to characters for comparison (1 token ≈ 4 chars)
const maxSizeInChars = data.maxSize * 4
return data.minSize < maxSizeInChars
},
{
message: 'Min chunk size (characters) must be less than max chunk size (tokens × 4)',
}
)
.optional(),
})
export async function GET(_request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
export async function GET(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -154,10 +133,7 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
}
}
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
export async function DELETE(_req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params

View File

@@ -139,8 +139,8 @@ describe('Knowledge Base API Route', () => {
const invalidData = {
name: 'Test KB',
chunkingConfig: {
maxSize: 100, // 100 tokens = 400 characters
minSize: 500, // Invalid: minSize (500 chars) > maxSize (400 chars)
maxSize: 100,
minSize: 200, // Invalid: minSize > maxSize
overlap: 50,
},
}
@@ -168,7 +168,7 @@ describe('Knowledge Base API Route', () => {
expect(data.data.embeddingDimension).toBe(1536)
expect(data.data.chunkingConfig).toEqual({
maxSize: 1024,
minSize: 100,
minSize: 1,
overlap: 200,
})
})

View File

@@ -7,14 +7,6 @@ import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('KnowledgeBaseAPI')
/**
* Schema for creating a knowledge base
*
* Chunking config units:
* - maxSize: tokens (1 token ≈ 4 characters)
* - minSize: characters
* - overlap: tokens (1 token ≈ 4 characters)
*/
const CreateKnowledgeBaseSchema = z.object({
name: z.string().min(1, 'Name is required'),
description: z.string().optional(),
@@ -23,28 +15,18 @@ const CreateKnowledgeBaseSchema = z.object({
embeddingDimension: z.literal(1536).default(1536),
chunkingConfig: z
.object({
/** Maximum chunk size in tokens (1 token ≈ 4 characters) */
maxSize: z.number().min(100).max(4000).default(1024),
/** Minimum chunk size in characters */
minSize: z.number().min(1).max(2000).default(100),
/** Overlap between chunks in tokens (1 token ≈ 4 characters) */
minSize: z.number().min(1).max(2000).default(1),
overlap: z.number().min(0).max(500).default(200),
})
.default({
maxSize: 1024,
minSize: 100,
minSize: 1,
overlap: 200,
})
.refine(
(data) => {
// Convert maxSize from tokens to characters for comparison (1 token ≈ 4 chars)
const maxSizeInChars = data.maxSize * 4
return data.minSize < maxSizeInChars
},
{
message: 'Min chunk size (characters) must be less than max chunk size (tokens × 4)',
}
),
.refine((data) => data.minSize < data.maxSize, {
message: 'Min chunk size must be less than max chunk size',
}),
})
export async function GET(req: NextRequest) {

View File

@@ -64,11 +64,6 @@ vi.mock('@/app/api/knowledge/utils', () => ({
checkKnowledgeBaseAccess: mockCheckKnowledgeBaseAccess,
}))
const mockGetDocumentTagDefinitions = vi.fn()
vi.mock('@/lib/knowledge/tags/service', () => ({
getDocumentTagDefinitions: mockGetDocumentTagDefinitions,
}))
const mockHandleTagOnlySearch = vi.fn()
const mockHandleVectorOnlySearch = vi.fn()
const mockHandleTagAndVectorSearch = vi.fn()
@@ -161,7 +156,6 @@ describe('Knowledge Search API Route', () => {
doc1: 'Document 1',
doc2: 'Document 2',
})
mockGetDocumentTagDefinitions.mockClear()
vi.stubGlobal('crypto', {
randomUUID: vi.fn().mockReturnValue('mock-uuid-1234-5678'),
@@ -665,8 +659,8 @@ describe('Knowledge Search API Route', () => {
describe('Optional Query Search', () => {
const mockTagDefinitions = [
{ tagSlot: 'tag1', displayName: 'category', fieldType: 'text' },
{ tagSlot: 'tag2', displayName: 'priority', fieldType: 'text' },
{ tagSlot: 'tag1', displayName: 'category' },
{ tagSlot: 'tag2', displayName: 'priority' },
]
const mockTaggedResults = [
@@ -695,7 +689,9 @@ describe('Knowledge Search API Route', () => {
it('should perform tag-only search without query', async () => {
const tagOnlyData = {
knowledgeBaseIds: 'kb-123',
tagFilters: [{ tagName: 'category', value: 'api', fieldType: 'text', operator: 'eq' }],
filters: {
category: 'api',
},
topK: 10,
}
@@ -710,11 +706,10 @@ describe('Knowledge Search API Route', () => {
},
})
// Mock tag definitions for validation
mockGetDocumentTagDefinitions.mockResolvedValue(mockTagDefinitions)
// Mock tag definitions queries for display mapping
mockDbChain.limit.mockResolvedValueOnce(mockTagDefinitions)
// Mock tag definitions queries for filter mapping and display mapping
mockDbChain.limit
.mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for filter mapping
.mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for display mapping
// Mock the tag-only search handler
mockHandleTagOnlySearch.mockResolvedValue(mockTaggedResults)
@@ -734,9 +729,7 @@ describe('Knowledge Search API Route', () => {
expect(mockHandleTagOnlySearch).toHaveBeenCalledWith({
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [
{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api', valueTo: undefined },
],
filters: { category: 'api' }, // Note: When no tag definitions are found, it uses the original filter key
})
})
@@ -744,7 +737,9 @@ describe('Knowledge Search API Route', () => {
const combinedData = {
knowledgeBaseIds: 'kb-123',
query: 'test search',
tagFilters: [{ tagName: 'category', value: 'api', fieldType: 'text', operator: 'eq' }],
filters: {
category: 'api',
},
topK: 10,
}
@@ -759,11 +754,10 @@ describe('Knowledge Search API Route', () => {
},
})
// Mock tag definitions for validation
mockGetDocumentTagDefinitions.mockResolvedValue(mockTagDefinitions)
// Mock tag definitions queries for display mapping
mockDbChain.limit.mockResolvedValueOnce(mockTagDefinitions)
// Mock tag definitions queries for filter mapping and display mapping
mockDbChain.limit
.mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for filter mapping
.mockResolvedValueOnce(mockTagDefinitions) // Tag definitions for display mapping
// Mock the tag + vector search handler
mockHandleTagAndVectorSearch.mockResolvedValue(mockSearchResults)
@@ -790,9 +784,7 @@ describe('Knowledge Search API Route', () => {
expect(mockHandleTagAndVectorSearch).toHaveBeenCalledWith({
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [
{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api', valueTo: undefined },
],
filters: { category: 'api' }, // Note: When no tag definitions are found, it uses the original filter key
queryVector: JSON.stringify(mockEmbedding),
distanceThreshold: 1, // Single KB uses threshold of 1.0
})
@@ -936,10 +928,10 @@ describe('Knowledge Search API Route', () => {
it('should handle tag-only search with multiple knowledge bases', async () => {
const multiKbTagData = {
knowledgeBaseIds: ['kb-123', 'kb-456'],
tagFilters: [
{ tagName: 'category', value: 'docs', fieldType: 'text', operator: 'eq' },
{ tagName: 'priority', value: 'high', fieldType: 'text', operator: 'eq' },
],
filters: {
category: 'docs',
priority: 'high',
},
topK: 10,
}
@@ -959,14 +951,37 @@ describe('Knowledge Search API Route', () => {
knowledgeBase: { id: 'kb-456', userId: 'user-123', name: 'Test KB 2' },
})
// Mock tag definitions for validation
mockGetDocumentTagDefinitions.mockResolvedValue(mockTagDefinitions)
// Reset all mocks before setting up specific behavior
Object.values(mockDbChain).forEach((fn) => {
if (typeof fn === 'function') {
fn.mockClear().mockReturnThis()
}
})
// Mock the tag-only search handler
mockHandleTagOnlySearch.mockResolvedValue(mockTaggedResults)
// Create fresh mocks for multiple database calls needed for multi-KB tag search
const mockTagDefsQuery1 = {
...mockDbChain,
limit: vi.fn().mockResolvedValue(mockTagDefinitions),
}
const mockTagSearchQuery = {
...mockDbChain,
limit: vi.fn().mockResolvedValue(mockTaggedResults),
}
const mockTagDefsQuery2 = {
...mockDbChain,
limit: vi.fn().mockResolvedValue(mockTagDefinitions),
}
const mockTagDefsQuery3 = {
...mockDbChain,
limit: vi.fn().mockResolvedValue(mockTagDefinitions),
}
// Mock tag definitions queries for display mapping
mockDbChain.limit.mockResolvedValueOnce(mockTagDefinitions)
// Chain the mocks for: tag defs, search, display mapping KB1, display mapping KB2
mockDbChain.select
.mockReturnValueOnce(mockTagDefsQuery1)
.mockReturnValueOnce(mockTagSearchQuery)
.mockReturnValueOnce(mockTagDefsQuery2)
.mockReturnValueOnce(mockTagDefsQuery3)
const req = createMockRequest('POST', multiKbTagData)
const { POST } = await import('@/app/api/knowledge/search/route')
@@ -1061,11 +1076,6 @@ describe('Knowledge Search API Route', () => {
},
})
// Mock tag definitions for validation
mockGetDocumentTagDefinitions.mockResolvedValue([
{ tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' },
])
mockHandleTagOnlySearch.mockResolvedValue([
{
id: 'chunk-2',
@@ -1098,15 +1108,13 @@ describe('Knowledge Search API Route', () => {
const mockTagDefs = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi
.fn()
.mockResolvedValue([{ tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' }]),
where: vi.fn().mockResolvedValue([]),
}
mockDbChain.select.mockReturnValueOnce(mockTagDefs)
const req = createMockRequest('POST', {
knowledgeBaseIds: ['kb-123'],
tagFilters: [{ tagName: 'tag1', value: 'api', fieldType: 'text', operator: 'eq' }],
filters: { tag1: 'api' },
topK: 10,
})
@@ -1135,11 +1143,6 @@ describe('Knowledge Search API Route', () => {
},
})
// Mock tag definitions for validation
mockGetDocumentTagDefinitions.mockResolvedValue([
{ tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' },
])
mockHandleTagAndVectorSearch.mockResolvedValue([
{
id: 'chunk-3',
@@ -1173,16 +1176,14 @@ describe('Knowledge Search API Route', () => {
const mockTagDefs = {
select: vi.fn().mockReturnThis(),
from: vi.fn().mockReturnThis(),
where: vi
.fn()
.mockResolvedValue([{ tagSlot: 'tag1', displayName: 'tag1', fieldType: 'text' }]),
where: vi.fn().mockResolvedValue([]),
}
mockDbChain.select.mockReturnValueOnce(mockTagDefs)
const req = createMockRequest('POST', {
knowledgeBaseIds: ['kb-123'],
query: 'relevant content',
tagFilters: [{ tagName: 'tag1', value: 'guide', fieldType: 'text', operator: 'eq' }],
filters: { tag1: 'guide' },
topK: 10,
})

View File

@@ -1,10 +1,8 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { generateRequestId } from '@/lib/core/utils/request'
import { ALL_TAG_SLOTS } from '@/lib/knowledge/constants'
import { TAG_SLOTS } from '@/lib/knowledge/constants'
import { getDocumentTagDefinitions } from '@/lib/knowledge/tags/service'
import { buildUndefinedTagsError, validateTagValue } from '@/lib/knowledge/tags/utils'
import type { StructuredFilter } from '@/lib/knowledge/types'
import { createLogger } from '@/lib/logs/console/logger'
import { estimateTokenCount } from '@/lib/tokenization/estimators'
import { getUserId } from '@/app/api/auth/oauth/utils'
@@ -22,16 +20,6 @@ import { calculateCost } from '@/providers/utils'
const logger = createLogger('VectorSearchAPI')
/** Structured tag filter with operator support */
const StructuredTagFilterSchema = z.object({
tagName: z.string(),
tagSlot: z.string().optional(),
fieldType: z.enum(['text', 'number', 'date', 'boolean']).default('text'),
operator: z.string().default('eq'),
value: z.union([z.string(), z.number(), z.boolean()]),
valueTo: z.union([z.string(), z.number()]).optional(),
})
const VectorSearchSchema = z
.object({
knowledgeBaseIds: z.union([
@@ -51,17 +39,18 @@ const VectorSearchSchema = z
.nullable()
.default(10)
.transform((val) => val ?? 10),
tagFilters: z
.array(StructuredTagFilterSchema)
filters: z
.record(z.string())
.optional()
.nullable()
.transform((val) => val || undefined),
.transform((val) => val || undefined), // Allow dynamic filter keys (display names)
})
.refine(
(data) => {
// Ensure at least query or filters are provided
const hasQuery = data.query && data.query.trim().length > 0
const hasTagFilters = data.tagFilters && data.tagFilters.length > 0
return hasQuery || hasTagFilters
const hasFilters = data.filters && Object.keys(data.filters).length > 0
return hasQuery || hasFilters
},
{
message: 'Please provide either a search query or tag filters to search your knowledge base',
@@ -99,81 +88,45 @@ export async function POST(request: NextRequest) {
)
// Map display names to tag slots for filtering
let structuredFilters: StructuredFilter[] = []
let mappedFilters: Record<string, string> = {}
if (validatedData.filters && accessibleKbIds.length > 0) {
try {
// Fetch tag definitions for the first accessible KB (since we're using single KB now)
const kbId = accessibleKbIds[0]
const tagDefs = await getDocumentTagDefinitions(kbId)
// Handle tag filters
if (validatedData.tagFilters && accessibleKbIds.length > 0) {
const kbId = accessibleKbIds[0]
const tagDefs = await getDocumentTagDefinitions(kbId)
logger.debug(`[${requestId}] Found tag definitions:`, tagDefs)
logger.debug(`[${requestId}] Original filters:`, validatedData.filters)
// Create mapping from display name to tag slot and fieldType
const displayNameToTagDef: Record<string, { tagSlot: string; fieldType: string }> = {}
tagDefs.forEach((def) => {
displayNameToTagDef[def.displayName] = {
tagSlot: def.tagSlot,
fieldType: def.fieldType,
}
})
// Create mapping from display name to tag slot
const displayNameToSlot: Record<string, string> = {}
tagDefs.forEach((def) => {
displayNameToSlot[def.displayName] = def.tagSlot
})
// Validate all tag filters first
const undefinedTags: string[] = []
const typeErrors: string[] = []
// Map the filters and handle OR logic
Object.entries(validatedData.filters).forEach(([key, value]) => {
if (value) {
const tagSlot = displayNameToSlot[key] || key // Fallback to key if no mapping found
for (const filter of validatedData.tagFilters) {
const tagDef = displayNameToTagDef[filter.tagName]
// Check if this is an OR filter (contains |OR| separator)
if (value.includes('|OR|')) {
logger.debug(
`[${requestId}] OR filter detected: "${key}" -> "${tagSlot}" = "${value}"`
)
}
// Check if tag exists
if (!tagDef) {
undefinedTags.push(filter.tagName)
continue
}
mappedFilters[tagSlot] = value
logger.debug(`[${requestId}] Mapped filter: "${key}" -> "${tagSlot}" = "${value}"`)
}
})
// Validate value type using shared validation
const validationError = validateTagValue(
filter.tagName,
String(filter.value),
tagDef.fieldType
)
if (validationError) {
typeErrors.push(validationError)
}
logger.debug(`[${requestId}] Final mapped filters:`, mappedFilters)
} catch (error) {
logger.error(`[${requestId}] Filter mapping error:`, error)
// If mapping fails, use original filters
mappedFilters = validatedData.filters
}
// Throw combined error if there are any validation issues
if (undefinedTags.length > 0 || typeErrors.length > 0) {
const errorParts: string[] = []
if (undefinedTags.length > 0) {
errorParts.push(buildUndefinedTagsError(undefinedTags))
}
if (typeErrors.length > 0) {
errorParts.push(...typeErrors)
}
return NextResponse.json({ error: errorParts.join('\n') }, { status: 400 })
}
// Build structured filters with validated data
structuredFilters = validatedData.tagFilters.map((filter) => {
const tagDef = displayNameToTagDef[filter.tagName]!
const tagSlot = filter.tagSlot || tagDef.tagSlot
const fieldType = filter.fieldType || tagDef.fieldType
logger.debug(
`[${requestId}] Structured filter: ${filter.tagName} -> ${tagSlot} (${fieldType}) ${filter.operator} ${filter.value}`
)
return {
tagSlot,
fieldType,
operator: filter.operator,
value: filter.value,
valueTo: filter.valueTo,
}
})
logger.debug(`[${requestId}] Processed ${structuredFilters.length} structured filters`)
}
if (accessibleKbIds.length === 0) {
@@ -202,29 +155,26 @@ export async function POST(request: NextRequest) {
let results: SearchResult[]
const hasFilters = structuredFilters && structuredFilters.length > 0
const hasFilters = mappedFilters && Object.keys(mappedFilters).length > 0
if (!hasQuery && hasFilters) {
// Tag-only search without vector similarity
logger.debug(`[${requestId}] Executing tag-only search with filters:`, structuredFilters)
logger.debug(`[${requestId}] Executing tag-only search with filters:`, mappedFilters)
results = await handleTagOnlySearch({
knowledgeBaseIds: accessibleKbIds,
topK: validatedData.topK,
structuredFilters,
filters: mappedFilters,
})
} else if (hasQuery && hasFilters) {
// Tag + Vector search
logger.debug(
`[${requestId}] Executing tag + vector search with filters:`,
structuredFilters
)
logger.debug(`[${requestId}] Executing tag + vector search with filters:`, mappedFilters)
const strategy = getQueryStrategy(accessibleKbIds.length, validatedData.topK)
const queryVector = JSON.stringify(await queryEmbeddingPromise)
results = await handleTagAndVectorSearch({
knowledgeBaseIds: accessibleKbIds,
topK: validatedData.topK,
structuredFilters,
filters: mappedFilters,
queryVector,
distanceThreshold: strategy.distanceThreshold,
})
@@ -307,9 +257,9 @@ export async function POST(request: NextRequest) {
// Create tags object with display names
const tags: Record<string, any> = {}
ALL_TAG_SLOTS.forEach((slot) => {
TAG_SLOTS.forEach((slot) => {
const tagValue = (result as any)[slot]
if (tagValue !== null && tagValue !== undefined) {
if (tagValue) {
const displayName = kbTagMap[slot] || slot
logger.debug(
`[${requestId}] Mapping ${slot}="${tagValue}" -> "${displayName}"="${tagValue}"`

View File

@@ -54,7 +54,7 @@ describe('Knowledge Search Utils', () => {
const params = {
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [],
filters: {},
}
await expect(handleTagOnlySearch(params)).rejects.toThrow(
@@ -66,14 +66,14 @@ describe('Knowledge Search Utils', () => {
const params = {
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }],
filters: { tag1: 'api' },
}
// This test validates the function accepts the right parameters
// The actual database interaction is tested via route tests
expect(params.knowledgeBaseIds).toEqual(['kb-123'])
expect(params.topK).toBe(10)
expect(params.structuredFilters).toHaveLength(1)
expect(params.filters).toEqual({ tag1: 'api' })
})
})
@@ -123,7 +123,7 @@ describe('Knowledge Search Utils', () => {
const params = {
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [],
filters: {},
queryVector: JSON.stringify([0.1, 0.2, 0.3]),
distanceThreshold: 0.8,
}
@@ -137,7 +137,7 @@ describe('Knowledge Search Utils', () => {
const params = {
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }],
filters: { tag1: 'api' },
distanceThreshold: 0.8,
}
@@ -150,7 +150,7 @@ describe('Knowledge Search Utils', () => {
const params = {
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }],
filters: { tag1: 'api' },
queryVector: JSON.stringify([0.1, 0.2, 0.3]),
}
@@ -163,7 +163,7 @@ describe('Knowledge Search Utils', () => {
const params = {
knowledgeBaseIds: ['kb-123'],
topK: 10,
structuredFilters: [{ tagSlot: 'tag1', fieldType: 'text', operator: 'eq', value: 'api' }],
filters: { tag1: 'api' },
queryVector: JSON.stringify([0.1, 0.2, 0.3]),
distanceThreshold: 0.8,
}
@@ -171,7 +171,7 @@ describe('Knowledge Search Utils', () => {
// This test validates the function accepts the right parameters
expect(params.knowledgeBaseIds).toEqual(['kb-123'])
expect(params.topK).toBe(10)
expect(params.structuredFilters).toHaveLength(1)
expect(params.filters).toEqual({ tag1: 'api' })
expect(params.queryVector).toBe(JSON.stringify([0.1, 0.2, 0.3]))
expect(params.distanceThreshold).toBe(0.8)
})

View File

@@ -1,7 +1,6 @@
import { db } from '@sim/db'
import { document, embedding } from '@sim/db/schema'
import { and, eq, inArray, isNull, sql } from 'drizzle-orm'
import type { StructuredFilter } from '@/lib/knowledge/types'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('KnowledgeSearchUtils')
@@ -35,7 +34,6 @@ export interface SearchResult {
content: string
documentId: string
chunkIndex: number
// Text tags
tag1: string | null
tag2: string | null
tag3: string | null
@@ -43,19 +41,6 @@ export interface SearchResult {
tag5: string | null
tag6: string | null
tag7: string | null
// Number tags (5 slots)
number1: number | null
number2: number | null
number3: number | null
number4: number | null
number5: number | null
// Date tags (2 slots)
date1: Date | null
date2: Date | null
// Boolean tags (3 slots)
boolean1: boolean | null
boolean2: boolean | null
boolean3: boolean | null
distance: number
knowledgeBaseId: string
}
@@ -63,7 +48,7 @@ export interface SearchResult {
export interface SearchParams {
knowledgeBaseIds: string[]
topK: number
structuredFilters?: StructuredFilter[]
filters?: Record<string, string>
queryVector?: string
distanceThreshold?: number
}
@@ -71,230 +56,46 @@ export interface SearchParams {
// Use shared embedding utility
export { generateSearchEmbedding } from '@/lib/knowledge/embeddings'
/** All valid tag slot keys */
const TAG_SLOT_KEYS = [
// Text tags (7 slots)
'tag1',
'tag2',
'tag3',
'tag4',
'tag5',
'tag6',
'tag7',
// Number tags (5 slots)
'number1',
'number2',
'number3',
'number4',
'number5',
// Date tags (2 slots)
'date1',
'date2',
// Boolean tags (3 slots)
'boolean1',
'boolean2',
'boolean3',
] as const
function getTagFilters(filters: Record<string, string>, embedding: any) {
return Object.entries(filters).map(([key, value]) => {
// Handle OR logic within same tag
const values = value.includes('|OR|') ? value.split('|OR|') : [value]
logger.debug(`[getTagFilters] Processing ${key}="${value}" -> values:`, values)
type TagSlotKey = (typeof TAG_SLOT_KEYS)[number]
function isTagSlotKey(key: string): key is TagSlotKey {
return TAG_SLOT_KEYS.includes(key as TagSlotKey)
}
/** Common fields selected for search results */
const getSearchResultFields = (distanceExpr: any) => ({
id: embedding.id,
content: embedding.content,
documentId: embedding.documentId,
chunkIndex: embedding.chunkIndex,
// Text tags
tag1: embedding.tag1,
tag2: embedding.tag2,
tag3: embedding.tag3,
tag4: embedding.tag4,
tag5: embedding.tag5,
tag6: embedding.tag6,
tag7: embedding.tag7,
// Number tags (5 slots)
number1: embedding.number1,
number2: embedding.number2,
number3: embedding.number3,
number4: embedding.number4,
number5: embedding.number5,
// Date tags (2 slots)
date1: embedding.date1,
date2: embedding.date2,
// Boolean tags (3 slots)
boolean1: embedding.boolean1,
boolean2: embedding.boolean2,
boolean3: embedding.boolean3,
distance: distanceExpr,
knowledgeBaseId: embedding.knowledgeBaseId,
})
/**
* Build a single SQL condition for a filter
*/
function buildFilterCondition(filter: StructuredFilter, embeddingTable: any) {
const { tagSlot, fieldType, operator, value, valueTo } = filter
if (!isTagSlotKey(tagSlot)) {
logger.debug(`[getStructuredTagFilters] Unknown tag slot: ${tagSlot}`)
return null
}
const column = embeddingTable[tagSlot]
if (!column) return null
logger.debug(
`[getStructuredTagFilters] Processing ${tagSlot} (${fieldType}) ${operator} ${value}`
)
// Handle text operators
if (fieldType === 'text') {
const stringValue = String(value)
switch (operator) {
case 'eq':
return sql`LOWER(${column}) = LOWER(${stringValue})`
case 'neq':
return sql`LOWER(${column}) != LOWER(${stringValue})`
case 'contains':
return sql`LOWER(${column}) LIKE LOWER(${`%${stringValue}%`})`
case 'not_contains':
return sql`LOWER(${column}) NOT LIKE LOWER(${`%${stringValue}%`})`
case 'starts_with':
return sql`LOWER(${column}) LIKE LOWER(${`${stringValue}%`})`
case 'ends_with':
return sql`LOWER(${column}) LIKE LOWER(${`%${stringValue}`})`
default:
return sql`LOWER(${column}) = LOWER(${stringValue})`
}
}
// Handle number operators
if (fieldType === 'number') {
const numValue = typeof value === 'number' ? value : Number.parseFloat(String(value))
if (Number.isNaN(numValue)) return null
switch (operator) {
case 'eq':
return sql`${column} = ${numValue}`
case 'neq':
return sql`${column} != ${numValue}`
case 'gt':
return sql`${column} > ${numValue}`
case 'gte':
return sql`${column} >= ${numValue}`
case 'lt':
return sql`${column} < ${numValue}`
case 'lte':
return sql`${column} <= ${numValue}`
case 'between':
if (valueTo !== undefined) {
const numValueTo =
typeof valueTo === 'number' ? valueTo : Number.parseFloat(String(valueTo))
if (Number.isNaN(numValueTo)) return sql`${column} = ${numValue}`
return sql`${column} >= ${numValue} AND ${column} <= ${numValueTo}`
}
return sql`${column} = ${numValue}`
default:
return sql`${column} = ${numValue}`
}
}
// Handle date operators - expects YYYY-MM-DD format from frontend
if (fieldType === 'date') {
const dateStr = String(value)
// Validate YYYY-MM-DD format
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
logger.debug(`[getStructuredTagFilters] Invalid date format: ${dateStr}, expected YYYY-MM-DD`)
return null
const getColumnForKey = (key: string) => {
switch (key) {
case 'tag1':
return embedding.tag1
case 'tag2':
return embedding.tag2
case 'tag3':
return embedding.tag3
case 'tag4':
return embedding.tag4
case 'tag5':
return embedding.tag5
case 'tag6':
return embedding.tag6
case 'tag7':
return embedding.tag7
default:
return null
}
}
switch (operator) {
case 'eq':
return sql`${column}::date = ${dateStr}::date`
case 'neq':
return sql`${column}::date != ${dateStr}::date`
case 'gt':
return sql`${column}::date > ${dateStr}::date`
case 'gte':
return sql`${column}::date >= ${dateStr}::date`
case 'lt':
return sql`${column}::date < ${dateStr}::date`
case 'lte':
return sql`${column}::date <= ${dateStr}::date`
case 'between':
if (valueTo !== undefined) {
const dateStrTo = String(valueTo)
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStrTo)) {
return sql`${column}::date = ${dateStr}::date`
}
return sql`${column}::date >= ${dateStr}::date AND ${column}::date <= ${dateStrTo}::date`
}
return sql`${column}::date = ${dateStr}::date`
default:
return sql`${column}::date = ${dateStr}::date`
const column = getColumnForKey(key)
if (!column) return sql`1=1` // No-op for unknown keys
if (values.length === 1) {
// Single value - simple equality
logger.debug(`[getTagFilters] Single value filter: ${key} = ${values[0]}`)
return sql`LOWER(${column}) = LOWER(${values[0]})`
}
}
// Handle boolean operators
if (fieldType === 'boolean') {
const boolValue = value === true || value === 'true'
switch (operator) {
case 'eq':
return sql`${column} = ${boolValue}`
case 'neq':
return sql`${column} != ${boolValue}`
default:
return sql`${column} = ${boolValue}`
}
}
// Fallback to equality
return sql`${column} = ${value}`
}
/**
* Build SQL conditions from structured filters with operator support
* - Same tag multiple times: OR logic
* - Different tags: AND logic
*/
function getStructuredTagFilters(filters: StructuredFilter[], embeddingTable: any) {
// Group filters by tagSlot
const filtersBySlot = new Map<string, StructuredFilter[]>()
for (const filter of filters) {
const slot = filter.tagSlot
if (!filtersBySlot.has(slot)) {
filtersBySlot.set(slot, [])
}
filtersBySlot.get(slot)!.push(filter)
}
// Build conditions: OR within same slot, AND across different slots
const conditions: ReturnType<typeof sql>[] = []
for (const [slot, slotFilters] of filtersBySlot) {
const slotConditions = slotFilters
.map((f) => buildFilterCondition(f, embeddingTable))
.filter((c): c is ReturnType<typeof sql> => c !== null)
if (slotConditions.length === 0) continue
if (slotConditions.length === 1) {
// Single condition for this slot
conditions.push(slotConditions[0])
} else {
// Multiple conditions for same slot - OR them together
logger.debug(
`[getStructuredTagFilters] OR'ing ${slotConditions.length} conditions for ${slot}`
)
conditions.push(sql`(${sql.join(slotConditions, sql` OR `)})`)
}
}
return conditions
// Multiple values - OR logic
logger.debug(`[getTagFilters] OR filter: ${key} IN (${values.join(', ')})`)
const orConditions = values.map((v) => sql`LOWER(${column}) = LOWER(${v})`)
return sql`(${sql.join(orConditions, sql` OR `)})`
})
}
export function getQueryStrategy(kbCount: number, topK: number) {
@@ -312,10 +113,8 @@ export function getQueryStrategy(kbCount: number, topK: number) {
async function executeTagFilterQuery(
knowledgeBaseIds: string[],
structuredFilters: StructuredFilter[]
filters: Record<string, string>
): Promise<{ id: string }[]> {
const tagFilterConditions = getStructuredTagFilters(structuredFilters, embedding)
if (knowledgeBaseIds.length === 1) {
return await db
.select({ id: embedding.id })
@@ -326,7 +125,7 @@ async function executeTagFilterQuery(
eq(embedding.knowledgeBaseId, knowledgeBaseIds[0]),
eq(embedding.enabled, true),
isNull(document.deletedAt),
...tagFilterConditions
...getTagFilters(filters, embedding)
)
)
}
@@ -339,7 +138,7 @@ async function executeTagFilterQuery(
inArray(embedding.knowledgeBaseId, knowledgeBaseIds),
eq(embedding.enabled, true),
isNull(document.deletedAt),
...tagFilterConditions
...getTagFilters(filters, embedding)
)
)
}
@@ -355,11 +154,21 @@ async function executeVectorSearchOnIds(
}
return await db
.select(
getSearchResultFields(
sql<number>`${embedding.embedding} <=> ${queryVector}::vector`.as('distance')
)
)
.select({
id: embedding.id,
content: embedding.content,
documentId: embedding.documentId,
chunkIndex: embedding.chunkIndex,
tag1: embedding.tag1,
tag2: embedding.tag2,
tag3: embedding.tag3,
tag4: embedding.tag4,
tag5: embedding.tag5,
tag6: embedding.tag6,
tag7: embedding.tag7,
distance: sql<number>`${embedding.embedding} <=> ${queryVector}::vector`.as('distance'),
knowledgeBaseId: embedding.knowledgeBaseId,
})
.from(embedding)
.innerJoin(document, eq(embedding.documentId, document.id))
.where(
@@ -374,16 +183,15 @@ async function executeVectorSearchOnIds(
}
export async function handleTagOnlySearch(params: SearchParams): Promise<SearchResult[]> {
const { knowledgeBaseIds, topK, structuredFilters } = params
const { knowledgeBaseIds, topK, filters } = params
if (!structuredFilters || structuredFilters.length === 0) {
if (!filters || Object.keys(filters).length === 0) {
throw new Error('Tag filters are required for tag-only search')
}
logger.debug(`[handleTagOnlySearch] Executing tag-only search with filters:`, structuredFilters)
logger.debug(`[handleTagOnlySearch] Executing tag-only search with filters:`, filters)
const strategy = getQueryStrategy(knowledgeBaseIds.length, topK)
const tagFilterConditions = getStructuredTagFilters(structuredFilters, embedding)
if (strategy.useParallel) {
// Parallel approach for many KBs
@@ -391,7 +199,21 @@ export async function handleTagOnlySearch(params: SearchParams): Promise<SearchR
const queryPromises = knowledgeBaseIds.map(async (kbId) => {
return await db
.select(getSearchResultFields(sql<number>`0`.as('distance')))
.select({
id: embedding.id,
content: embedding.content,
documentId: embedding.documentId,
chunkIndex: embedding.chunkIndex,
tag1: embedding.tag1,
tag2: embedding.tag2,
tag3: embedding.tag3,
tag4: embedding.tag4,
tag5: embedding.tag5,
tag6: embedding.tag6,
tag7: embedding.tag7,
distance: sql<number>`0`.as('distance'), // No distance for tag-only searches
knowledgeBaseId: embedding.knowledgeBaseId,
})
.from(embedding)
.innerJoin(document, eq(embedding.documentId, document.id))
.where(
@@ -399,7 +221,7 @@ export async function handleTagOnlySearch(params: SearchParams): Promise<SearchR
eq(embedding.knowledgeBaseId, kbId),
eq(embedding.enabled, true),
isNull(document.deletedAt),
...tagFilterConditions
...getTagFilters(filters, embedding)
)
)
.limit(parallelLimit)
@@ -410,7 +232,21 @@ export async function handleTagOnlySearch(params: SearchParams): Promise<SearchR
}
// Single query for fewer KBs
return await db
.select(getSearchResultFields(sql<number>`0`.as('distance')))
.select({
id: embedding.id,
content: embedding.content,
documentId: embedding.documentId,
chunkIndex: embedding.chunkIndex,
tag1: embedding.tag1,
tag2: embedding.tag2,
tag3: embedding.tag3,
tag4: embedding.tag4,
tag5: embedding.tag5,
tag6: embedding.tag6,
tag7: embedding.tag7,
distance: sql<number>`0`.as('distance'), // No distance for tag-only searches
knowledgeBaseId: embedding.knowledgeBaseId,
})
.from(embedding)
.innerJoin(document, eq(embedding.documentId, document.id))
.where(
@@ -418,7 +254,7 @@ export async function handleTagOnlySearch(params: SearchParams): Promise<SearchR
inArray(embedding.knowledgeBaseId, knowledgeBaseIds),
eq(embedding.enabled, true),
isNull(document.deletedAt),
...tagFilterConditions
...getTagFilters(filters, embedding)
)
)
.limit(topK)
@@ -435,15 +271,27 @@ export async function handleVectorOnlySearch(params: SearchParams): Promise<Sear
const strategy = getQueryStrategy(knowledgeBaseIds.length, topK)
const distanceExpr = sql<number>`${embedding.embedding} <=> ${queryVector}::vector`.as('distance')
if (strategy.useParallel) {
// Parallel approach for many KBs
const parallelLimit = Math.ceil(topK / knowledgeBaseIds.length) + 5
const queryPromises = knowledgeBaseIds.map(async (kbId) => {
return await db
.select(getSearchResultFields(distanceExpr))
.select({
id: embedding.id,
content: embedding.content,
documentId: embedding.documentId,
chunkIndex: embedding.chunkIndex,
tag1: embedding.tag1,
tag2: embedding.tag2,
tag3: embedding.tag3,
tag4: embedding.tag4,
tag5: embedding.tag5,
tag6: embedding.tag6,
tag7: embedding.tag7,
distance: sql<number>`${embedding.embedding} <=> ${queryVector}::vector`.as('distance'),
knowledgeBaseId: embedding.knowledgeBaseId,
})
.from(embedding)
.innerJoin(document, eq(embedding.documentId, document.id))
.where(
@@ -464,7 +312,21 @@ export async function handleVectorOnlySearch(params: SearchParams): Promise<Sear
}
// Single query for fewer KBs
return await db
.select(getSearchResultFields(distanceExpr))
.select({
id: embedding.id,
content: embedding.content,
documentId: embedding.documentId,
chunkIndex: embedding.chunkIndex,
tag1: embedding.tag1,
tag2: embedding.tag2,
tag3: embedding.tag3,
tag4: embedding.tag4,
tag5: embedding.tag5,
tag6: embedding.tag6,
tag7: embedding.tag7,
distance: sql<number>`${embedding.embedding} <=> ${queryVector}::vector`.as('distance'),
knowledgeBaseId: embedding.knowledgeBaseId,
})
.from(embedding)
.innerJoin(document, eq(embedding.documentId, document.id))
.where(
@@ -480,22 +342,19 @@ export async function handleVectorOnlySearch(params: SearchParams): Promise<Sear
}
export async function handleTagAndVectorSearch(params: SearchParams): Promise<SearchResult[]> {
const { knowledgeBaseIds, topK, structuredFilters, queryVector, distanceThreshold } = params
const { knowledgeBaseIds, topK, filters, queryVector, distanceThreshold } = params
if (!structuredFilters || structuredFilters.length === 0) {
if (!filters || Object.keys(filters).length === 0) {
throw new Error('Tag filters are required for tag and vector search')
}
if (!queryVector || !distanceThreshold) {
throw new Error('Query vector and distance threshold are required for tag and vector search')
}
logger.debug(
`[handleTagAndVectorSearch] Executing tag + vector search with filters:`,
structuredFilters
)
logger.debug(`[handleTagAndVectorSearch] Executing tag + vector search with filters:`, filters)
// Step 1: Filter by tags first
const tagFilteredIds = await executeTagFilterQuery(knowledgeBaseIds, structuredFilters)
const tagFilteredIds = await executeTagFilterQuery(knowledgeBaseIds, filters)
if (tagFilteredIds.length === 0) {
logger.debug(`[handleTagAndVectorSearch] No results found after tag filtering`)

View File

@@ -183,12 +183,7 @@ describe('Knowledge Utils', () => {
describe('processDocumentAsync', () => {
it.concurrent('should insert embeddings before updating document counters', async () => {
kbRows.push({
id: 'kb1',
userId: 'user1',
workspaceId: null,
chunkingConfig: { maxSize: 1024, minSize: 1, overlap: 200 },
})
kbRows.push({ id: 'kb1', userId: 'user1', workspaceId: null })
docRows.push({ id: 'doc1', knowledgeBaseId: 'kb1' })
await processDocumentAsync(

View File

@@ -35,7 +35,7 @@ export interface DocumentData {
enabled: boolean
deletedAt?: Date | null
uploadedAt: Date
// Text tags
// Document tags
tag1?: string | null
tag2?: string | null
tag3?: string | null
@@ -43,19 +43,6 @@ export interface DocumentData {
tag5?: string | null
tag6?: string | null
tag7?: string | null
// Number tags (5 slots)
number1?: number | null
number2?: number | null
number3?: number | null
number4?: number | null
number5?: number | null
// Date tags (2 slots)
date1?: Date | null
date2?: Date | null
// Boolean tags (3 slots)
boolean1?: boolean | null
boolean2?: boolean | null
boolean3?: boolean | null
}
export interface EmbeddingData {
@@ -71,7 +58,7 @@ export interface EmbeddingData {
embeddingModel: string
startOffset: number
endOffset: number
// Text tags
// Tag fields for filtering
tag1?: string | null
tag2?: string | null
tag3?: string | null
@@ -79,19 +66,6 @@ export interface EmbeddingData {
tag5?: string | null
tag6?: string | null
tag7?: string | null
// Number tags (5 slots)
number1?: number | null
number2?: number | null
number3?: number | null
number4?: number | null
number5?: number | null
// Date tags (2 slots)
date1?: Date | null
date2?: Date | null
// Boolean tags (3 slots)
boolean1?: boolean | null
boolean2?: boolean | null
boolean3?: boolean | null
enabled: boolean
createdAt: Date
updatedAt: Date
@@ -258,27 +232,6 @@ export async function checkDocumentWriteAccess(
processingStartedAt: document.processingStartedAt,
processingCompletedAt: document.processingCompletedAt,
knowledgeBaseId: document.knowledgeBaseId,
// Text tags
tag1: document.tag1,
tag2: document.tag2,
tag3: document.tag3,
tag4: document.tag4,
tag5: document.tag5,
tag6: document.tag6,
tag7: document.tag7,
// Number tags (5 slots)
number1: document.number1,
number2: document.number2,
number3: document.number3,
number4: document.number4,
number5: document.number5,
// Date tags (2 slots)
date1: document.date1,
date2: document.date2,
// Boolean tags (3 slots)
boolean1: document.boolean1,
boolean2: document.boolean2,
boolean3: document.boolean3,
})
.from(document)
.where(and(eq(document.id, documentId), isNull(document.deletedAt)))

View File

@@ -1,72 +1,32 @@
import { db } from '@sim/db'
import {
permissions,
workflow,
workflowExecutionLogs,
workflowExecutionSnapshots,
} from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { workflowExecutionLogs, workflowExecutionSnapshots } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('LogsByExecutionIdAPI')
export async function GET(
request: NextRequest,
_request: NextRequest,
{ params }: { params: Promise<{ executionId: string }> }
) {
const requestId = generateRequestId()
try {
const { executionId } = await params
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized execution data access attempt for: ${executionId}`)
return NextResponse.json(
{ error: authResult.error || 'Authentication required' },
{ status: 401 }
)
}
const authenticatedUserId = authResult.userId
logger.debug(
`[${requestId}] Fetching execution data for: ${executionId} (auth: ${authResult.authType})`
)
logger.debug(`Fetching execution data for: ${executionId}`)
// Get the workflow execution log to find the snapshot
const [workflowLog] = await db
.select({
id: workflowExecutionLogs.id,
workflowId: workflowExecutionLogs.workflowId,
executionId: workflowExecutionLogs.executionId,
stateSnapshotId: workflowExecutionLogs.stateSnapshotId,
trigger: workflowExecutionLogs.trigger,
startedAt: workflowExecutionLogs.startedAt,
endedAt: workflowExecutionLogs.endedAt,
totalDurationMs: workflowExecutionLogs.totalDurationMs,
cost: workflowExecutionLogs.cost,
})
.select()
.from(workflowExecutionLogs)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
permissions,
and(
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workflow.workspaceId),
eq(permissions.userId, authenticatedUserId)
)
)
.where(eq(workflowExecutionLogs.executionId, executionId))
.limit(1)
if (!workflowLog) {
logger.warn(`[${requestId}] Execution not found or access denied: ${executionId}`)
return NextResponse.json({ error: 'Workflow execution not found' }, { status: 404 })
}
// Get the workflow state snapshot
const [snapshot] = await db
.select()
.from(workflowExecutionSnapshots)
@@ -74,7 +34,6 @@ export async function GET(
.limit(1)
if (!snapshot) {
logger.warn(`[${requestId}] Workflow state snapshot not found for execution: ${executionId}`)
return NextResponse.json({ error: 'Workflow state snapshot not found' }, { status: 404 })
}
@@ -91,14 +50,14 @@ export async function GET(
},
}
logger.debug(`[${requestId}] Successfully fetched execution data for: ${executionId}`)
logger.debug(`Successfully fetched execution data for: ${executionId}`)
logger.debug(
`[${requestId}] Workflow state contains ${Object.keys((snapshot.stateData as any)?.blocks || {}).length} blocks`
`Workflow state contains ${Object.keys((snapshot.stateData as any)?.blocks || {}).length} blocks`
)
return NextResponse.json(response)
} catch (error) {
logger.error(`[${requestId}] Error fetching execution data:`, error)
logger.error('Error fetching execution data:', error)
return NextResponse.json({ error: 'Failed to fetch execution data' }, { status: 500 })
}
}

View File

@@ -57,7 +57,7 @@ export async function GET(request: NextRequest) {
workflowName: workflow.name,
}
let conditions: SQL | undefined = eq(workflowExecutionLogs.workspaceId, params.workspaceId)
let conditions: SQL | undefined = eq(workflow.workspaceId, params.workspaceId)
if (params.level && params.level !== 'all') {
const levels = params.level.split(',').filter(Boolean)
@@ -134,7 +134,7 @@ export async function GET(request: NextRequest) {
permissions,
and(
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
eq(permissions.entityId, workflow.workspaceId),
eq(permissions.userId, userId)
)
)

View File

@@ -130,8 +130,6 @@ export async function GET(request: NextRequest) {
deploymentVersionName: sql<null>`NULL`,
}
const workspaceFilter = eq(workflowExecutionLogs.workspaceId, params.workspaceId)
const baseQuery = db
.select(selectColumns)
.from(workflowExecutionLogs)
@@ -143,12 +141,18 @@ export async function GET(request: NextRequest) {
workflowDeploymentVersion,
eq(workflowDeploymentVersion.id, workflowExecutionLogs.deploymentVersionId)
)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
workflow,
and(
eq(workflowExecutionLogs.workflowId, workflow.id),
eq(workflow.workspaceId, params.workspaceId)
)
)
.innerJoin(
permissions,
and(
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
eq(permissions.entityId, workflow.workspaceId),
eq(permissions.userId, userId)
)
)
@@ -296,7 +300,7 @@ export async function GET(request: NextRequest) {
}
const logs = await baseQuery
.where(and(workspaceFilter, conditions))
.where(conditions)
.orderBy(desc(workflowExecutionLogs.startedAt))
.limit(params.limit)
.offset(params.offset)
@@ -308,16 +312,22 @@ export async function GET(request: NextRequest) {
pausedExecutions,
eq(pausedExecutions.executionId, workflowExecutionLogs.executionId)
)
.innerJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id))
.innerJoin(
workflow,
and(
eq(workflowExecutionLogs.workflowId, workflow.id),
eq(workflow.workspaceId, params.workspaceId)
)
)
.innerJoin(
permissions,
and(
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
eq(permissions.entityId, workflow.workspaceId),
eq(permissions.userId, userId)
)
)
.where(and(eq(workflowExecutionLogs.workspaceId, params.workspaceId), conditions))
.where(conditions)
const countResult = await countQuery

View File

@@ -1,5 +1,5 @@
import { db } from '@sim/db'
import { permissions, workflowExecutionLogs } from '@sim/db/schema'
import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
import { and, eq, isNotNull, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
@@ -42,17 +42,23 @@ export async function GET(request: NextRequest) {
trigger: workflowExecutionLogs.trigger,
})
.from(workflowExecutionLogs)
.innerJoin(
workflow,
and(
eq(workflowExecutionLogs.workflowId, workflow.id),
eq(workflow.workspaceId, params.workspaceId)
)
)
.innerJoin(
permissions,
and(
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workflowExecutionLogs.workspaceId),
eq(permissions.entityId, workflow.workspaceId),
eq(permissions.userId, userId)
)
)
.where(
and(
eq(workflowExecutionLogs.workspaceId, params.workspaceId),
isNotNull(workflowExecutionLogs.trigger),
sql`${workflowExecutionLogs.trigger} NOT IN ('api', 'manual', 'webhook', 'chat', 'schedule')`
)

View File

@@ -1,16 +1,52 @@
import { db } from '@sim/db'
import { memory, permissions, workspace } from '@sim/db/schema'
import { memory, workflowBlocks } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
const logger = createLogger('MemoryByIdAPI')
/**
* Parse memory key into conversationId and blockId
* Key format: conversationId:blockId
*/
function parseMemoryKey(key: string): { conversationId: string; blockId: string } | null {
const parts = key.split(':')
if (parts.length !== 2) {
return null
}
return {
conversationId: parts[0],
blockId: parts[1],
}
}
/**
* Lookup block name from block ID
*/
async function getBlockName(blockId: string, workflowId: string): Promise<string | undefined> {
try {
const result = await db
.select({ name: workflowBlocks.name })
.from(workflowBlocks)
.where(and(eq(workflowBlocks.id, blockId), eq(workflowBlocks.workflowId, workflowId)))
.limit(1)
if (result.length === 0) {
return undefined
}
return result[0].name
} catch (error) {
logger.error('Error looking up block name', { error, blockId, workflowId })
return undefined
}
}
const memoryQuerySchema = z.object({
workspaceId: z.string().uuid('Invalid workspace ID format'),
workflowId: z.string().uuid('Invalid workflow ID format'),
})
const agentMemoryDataSchema = z.object({
@@ -26,147 +62,114 @@ const memoryPutBodySchema = z.object({
data: z.union([agentMemoryDataSchema, genericMemoryDataSchema], {
errorMap: () => ({ message: 'Invalid memory data structure' }),
}),
workspaceId: z.string().uuid('Invalid workspace ID format'),
workflowId: z.string().uuid('Invalid workflow ID format'),
})
async function checkWorkspaceAccess(
workspaceId: string,
userId: string
): Promise<{ hasAccess: boolean; canWrite: boolean }> {
const [workspaceRow] = await db
.select({ ownerId: workspace.ownerId })
.from(workspace)
.where(eq(workspace.id, workspaceId))
.limit(1)
if (!workspaceRow) {
return { hasAccess: false, canWrite: false }
}
if (workspaceRow.ownerId === userId) {
return { hasAccess: true, canWrite: true }
}
const [permissionRow] = await db
.select({ permissionType: permissions.permissionType })
.from(permissions)
.where(
and(
eq(permissions.userId, userId),
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workspaceId)
)
)
.limit(1)
if (!permissionRow) {
return { hasAccess: false, canWrite: false }
}
return {
hasAccess: true,
canWrite: permissionRow.permissionType === 'write' || permissionRow.permissionType === 'admin',
}
}
async function validateMemoryAccess(
request: NextRequest,
workspaceId: string,
requestId: string,
action: 'read' | 'write'
): Promise<{ userId: string } | { error: NextResponse }> {
const authResult = await checkHybridAuth(request, { requireWorkflowId: false })
if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized memory ${action} attempt`)
return {
error: NextResponse.json(
{ success: false, error: { message: 'Authentication required' } },
{ status: 401 }
),
}
}
const { hasAccess, canWrite } = await checkWorkspaceAccess(workspaceId, authResult.userId)
if (!hasAccess) {
return {
error: NextResponse.json(
{ success: false, error: { message: 'Workspace not found' } },
{ status: 404 }
),
}
}
if (action === 'write' && !canWrite) {
return {
error: NextResponse.json(
{ success: false, error: { message: 'Write access denied' } },
{ status: 403 }
),
}
}
return { userId: authResult.userId }
}
export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'
/**
* GET handler for retrieving a specific memory by ID
*/
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
try {
const url = new URL(request.url)
const workspaceId = url.searchParams.get('workspaceId')
logger.info(`[${requestId}] Processing memory get request for ID: ${id}`)
const url = new URL(request.url)
const workflowId = url.searchParams.get('workflowId')
const validation = memoryQuerySchema.safeParse({ workflowId })
const validation = memoryQuerySchema.safeParse({ workspaceId })
if (!validation.success) {
const errorMessage = validation.error.errors
.map((err) => `${err.path.join('.')}: ${err.message}`)
.join(', ')
logger.warn(`[${requestId}] Validation error: ${errorMessage}`)
return NextResponse.json(
{ success: false, error: { message: errorMessage } },
{
success: false,
error: {
message: errorMessage,
},
},
{ status: 400 }
)
}
const { workspaceId: validatedWorkspaceId } = validation.data
const accessCheck = await validateMemoryAccess(request, validatedWorkspaceId, requestId, 'read')
if ('error' in accessCheck) {
return accessCheck.error
}
const { workflowId: validatedWorkflowId } = validation.data
const memories = await db
.select()
.from(memory)
.where(and(eq(memory.key, id), eq(memory.workspaceId, validatedWorkspaceId)))
.where(and(eq(memory.key, id), eq(memory.workflowId, validatedWorkflowId)))
.orderBy(memory.createdAt)
.limit(1)
if (memories.length === 0) {
logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${validatedWorkflowId}`)
return NextResponse.json(
{ success: false, error: { message: 'Memory not found' } },
{
success: false,
error: {
message: 'Memory not found',
},
},
{ status: 404 }
)
}
const mem = memories[0]
const parsed = parseMemoryKey(mem.key)
logger.info(`[${requestId}] Memory retrieved: ${id} for workspace: ${validatedWorkspaceId}`)
let enrichedMemory
if (!parsed) {
enrichedMemory = {
conversationId: mem.key,
blockId: 'unknown',
blockName: 'unknown',
data: mem.data,
}
} else {
const { conversationId, blockId } = parsed
const blockName = (await getBlockName(blockId, validatedWorkflowId)) || 'unknown'
enrichedMemory = {
conversationId,
blockId,
blockName,
data: mem.data,
}
}
logger.info(
`[${requestId}] Memory retrieved successfully: ${id} for workflow: ${validatedWorkflowId}`
)
return NextResponse.json(
{ success: true, data: { conversationId: mem.key, data: mem.data } },
{
success: true,
data: enrichedMemory,
},
{ status: 200 }
)
} catch (error: any) {
logger.error(`[${requestId}] Error retrieving memory`, { error })
return NextResponse.json(
{ success: false, error: { message: error.message || 'Failed to retrieve memory' } },
{
success: false,
error: {
message: error.message || 'Failed to retrieve memory',
},
},
{ status: 500 }
)
}
}
/**
* DELETE handler for removing a specific memory
*/
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
@@ -175,70 +178,89 @@ export async function DELETE(
const { id } = await params
try {
const url = new URL(request.url)
const workspaceId = url.searchParams.get('workspaceId')
logger.info(`[${requestId}] Processing memory delete request for ID: ${id}`)
const url = new URL(request.url)
const workflowId = url.searchParams.get('workflowId')
const validation = memoryQuerySchema.safeParse({ workflowId })
const validation = memoryQuerySchema.safeParse({ workspaceId })
if (!validation.success) {
const errorMessage = validation.error.errors
.map((err) => `${err.path.join('.')}: ${err.message}`)
.join(', ')
logger.warn(`[${requestId}] Validation error: ${errorMessage}`)
return NextResponse.json(
{ success: false, error: { message: errorMessage } },
{
success: false,
error: {
message: errorMessage,
},
},
{ status: 400 }
)
}
const { workspaceId: validatedWorkspaceId } = validation.data
const accessCheck = await validateMemoryAccess(
request,
validatedWorkspaceId,
requestId,
'write'
)
if ('error' in accessCheck) {
return accessCheck.error
}
const { workflowId: validatedWorkflowId } = validation.data
const existingMemory = await db
.select({ id: memory.id })
.from(memory)
.where(and(eq(memory.key, id), eq(memory.workspaceId, validatedWorkspaceId)))
.where(and(eq(memory.key, id), eq(memory.workflowId, validatedWorkflowId)))
.limit(1)
if (existingMemory.length === 0) {
logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${validatedWorkflowId}`)
return NextResponse.json(
{ success: false, error: { message: 'Memory not found' } },
{
success: false,
error: {
message: 'Memory not found',
},
},
{ status: 404 }
)
}
await db
.delete(memory)
.where(and(eq(memory.key, id), eq(memory.workspaceId, validatedWorkspaceId)))
.where(and(eq(memory.key, id), eq(memory.workflowId, validatedWorkflowId)))
logger.info(`[${requestId}] Memory deleted: ${id} for workspace: ${validatedWorkspaceId}`)
logger.info(
`[${requestId}] Memory deleted successfully: ${id} for workflow: ${validatedWorkflowId}`
)
return NextResponse.json(
{ success: true, data: { message: 'Memory deleted successfully' } },
{
success: true,
data: { message: 'Memory deleted successfully' },
},
{ status: 200 }
)
} catch (error: any) {
logger.error(`[${requestId}] Error deleting memory`, { error })
return NextResponse.json(
{ success: false, error: { message: error.message || 'Failed to delete memory' } },
{
success: false,
error: {
message: error.message || 'Failed to delete memory',
},
},
{ status: 500 }
)
}
}
/**
* PUT handler for updating a specific memory
*/
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
try {
logger.info(`[${requestId}] Processing memory update request for ID: ${id}`)
let validatedData
let validatedWorkspaceId
let validatedWorkflowId
try {
const body = await request.json()
const validation = memoryPutBodySchema.safeParse(body)
@@ -247,40 +269,48 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
const errorMessage = validation.error.errors
.map((err) => `${err.path.join('.')}: ${err.message}`)
.join(', ')
logger.warn(`[${requestId}] Validation error: ${errorMessage}`)
return NextResponse.json(
{ success: false, error: { message: `Invalid request body: ${errorMessage}` } },
{
success: false,
error: {
message: `Invalid request body: ${errorMessage}`,
},
},
{ status: 400 }
)
}
validatedData = validation.data.data
validatedWorkspaceId = validation.data.workspaceId
} catch {
validatedWorkflowId = validation.data.workflowId
} catch (error: any) {
logger.warn(`[${requestId}] Failed to parse request body: ${error.message}`)
return NextResponse.json(
{ success: false, error: { message: 'Invalid JSON in request body' } },
{
success: false,
error: {
message: 'Invalid JSON in request body',
},
},
{ status: 400 }
)
}
const accessCheck = await validateMemoryAccess(
request,
validatedWorkspaceId,
requestId,
'write'
)
if ('error' in accessCheck) {
return accessCheck.error
}
const existingMemories = await db
.select()
.from(memory)
.where(and(eq(memory.key, id), eq(memory.workspaceId, validatedWorkspaceId)))
.where(and(eq(memory.key, id), eq(memory.workflowId, validatedWorkflowId)))
.limit(1)
if (existingMemories.length === 0) {
logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${validatedWorkflowId}`)
return NextResponse.json(
{ success: false, error: { message: 'Memory not found' } },
{
success: false,
error: {
message: 'Memory not found',
},
},
{ status: 404 }
)
}
@@ -290,8 +320,14 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
const errorMessage = agentValidation.error.errors
.map((err) => `${err.path.join('.')}: ${err.message}`)
.join(', ')
logger.warn(`[${requestId}] Agent memory validation error: ${errorMessage}`)
return NextResponse.json(
{ success: false, error: { message: `Invalid agent memory data: ${errorMessage}` } },
{
success: false,
error: {
message: `Invalid agent memory data: ${errorMessage}`,
},
},
{ status: 400 }
)
}
@@ -299,26 +335,59 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
const now = new Date()
await db
.update(memory)
.set({ data: validatedData, updatedAt: now })
.where(and(eq(memory.key, id), eq(memory.workspaceId, validatedWorkspaceId)))
.set({
data: validatedData,
updatedAt: now,
})
.where(and(eq(memory.key, id), eq(memory.workflowId, validatedWorkflowId)))
const updatedMemories = await db
.select()
.from(memory)
.where(and(eq(memory.key, id), eq(memory.workspaceId, validatedWorkspaceId)))
.where(and(eq(memory.key, id), eq(memory.workflowId, validatedWorkflowId)))
.limit(1)
const mem = updatedMemories[0]
const parsed = parseMemoryKey(mem.key)
logger.info(`[${requestId}] Memory updated: ${id} for workspace: ${validatedWorkspaceId}`)
let enrichedMemory
if (!parsed) {
enrichedMemory = {
conversationId: mem.key,
blockId: 'unknown',
blockName: 'unknown',
data: mem.data,
}
} else {
const { conversationId, blockId } = parsed
const blockName = (await getBlockName(blockId, validatedWorkflowId)) || 'unknown'
enrichedMemory = {
conversationId,
blockId,
blockName,
data: mem.data,
}
}
logger.info(
`[${requestId}] Memory updated successfully: ${id} for workflow: ${validatedWorkflowId}`
)
return NextResponse.json(
{ success: true, data: { conversationId: mem.key, data: mem.data } },
{
success: true,
data: enrichedMemory,
},
{ status: 200 }
)
} catch (error: any) {
logger.error(`[${requestId}] Error updating memory`, { error })
return NextResponse.json(
{ success: false, error: { message: error.message || 'Failed to update memory' } },
{
success: false,
error: {
message: error.message || 'Failed to update memory',
},
},
{ status: 500 }
)
}

View File

@@ -1,56 +1,42 @@
import { db } from '@sim/db'
import { memory, permissions, workspace } from '@sim/db/schema'
import { and, eq, isNull, like } from 'drizzle-orm'
import { memory, workflowBlocks } from '@sim/db/schema'
import { and, eq, inArray, isNull, like } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { getWorkflowAccessContext } from '@/lib/workflows/utils'
const logger = createLogger('MemoryAPI')
export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'
async function checkWorkspaceAccess(
workspaceId: string,
userId: string
): Promise<{ hasAccess: boolean; canWrite: boolean }> {
const [workspaceRow] = await db
.select({ ownerId: workspace.ownerId })
.from(workspace)
.where(eq(workspace.id, workspaceId))
.limit(1)
if (!workspaceRow) {
return { hasAccess: false, canWrite: false }
/**
* Parse memory key into conversationId and blockId
* Key format: conversationId:blockId
* @param key The memory key to parse
* @returns Object with conversationId and blockId, or null if invalid
*/
function parseMemoryKey(key: string): { conversationId: string; blockId: string } | null {
const parts = key.split(':')
if (parts.length !== 2) {
return null
}
if (workspaceRow.ownerId === userId) {
return { hasAccess: true, canWrite: true }
}
const [permissionRow] = await db
.select({ permissionType: permissions.permissionType })
.from(permissions)
.where(
and(
eq(permissions.userId, userId),
eq(permissions.entityType, 'workspace'),
eq(permissions.entityId, workspaceId)
)
)
.limit(1)
if (!permissionRow) {
return { hasAccess: false, canWrite: false }
}
return {
hasAccess: true,
canWrite: permissionRow.permissionType === 'write' || permissionRow.permissionType === 'admin',
conversationId: parts[0],
blockId: parts[1],
}
}
/**
* GET handler for searching and retrieving memories
* Supports query parameters:
* - query: Search string for memory keys
* - type: Filter by memory type
* - limit: Maximum number of results (default: 50)
* - workflowId: Filter by workflow ID (required)
*/
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
@@ -59,32 +45,102 @@ export async function GET(request: NextRequest) {
if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized memory access attempt`)
return NextResponse.json(
{ success: false, error: { message: authResult.error || 'Authentication required' } },
{
success: false,
error: {
message: authResult.error || 'Authentication required',
},
},
{ status: 401 }
)
}
logger.info(`[${requestId}] Processing memory search request`)
const url = new URL(request.url)
const workspaceId = url.searchParams.get('workspaceId')
const workflowId = url.searchParams.get('workflowId')
const searchQuery = url.searchParams.get('query')
const blockNameFilter = url.searchParams.get('blockName')
const limit = Number.parseInt(url.searchParams.get('limit') || '50')
if (!workspaceId) {
if (!workflowId) {
logger.warn(`[${requestId}] Missing required parameter: workflowId`)
return NextResponse.json(
{ success: false, error: { message: 'workspaceId parameter is required' } },
{
success: false,
error: {
message: 'workflowId parameter is required',
},
},
{ status: 400 }
)
}
const { hasAccess } = await checkWorkspaceAccess(workspaceId, authResult.userId)
if (!hasAccess) {
const accessContext = await getWorkflowAccessContext(workflowId, authResult.userId)
if (!accessContext) {
logger.warn(`[${requestId}] Workflow ${workflowId} not found for user ${authResult.userId}`)
return NextResponse.json(
{ success: false, error: { message: 'Access denied to this workspace' } },
{
success: false,
error: {
message: 'Workflow not found',
},
},
{ status: 404 }
)
}
const { workspacePermission, isOwner } = accessContext
if (!isOwner && !workspacePermission) {
logger.warn(
`[${requestId}] User ${authResult.userId} denied access to workflow ${workflowId}`
)
return NextResponse.json(
{
success: false,
error: {
message: 'Access denied to this workflow',
},
},
{ status: 403 }
)
}
const conditions = [isNull(memory.deletedAt), eq(memory.workspaceId, workspaceId)]
logger.info(
`[${requestId}] User ${authResult.userId} (${authResult.authType}) accessing memories for workflow ${workflowId}`
)
const conditions = []
conditions.push(isNull(memory.deletedAt))
conditions.push(eq(memory.workflowId, workflowId))
let blockIdsToFilter: string[] | null = null
if (blockNameFilter) {
const blocks = await db
.select({ id: workflowBlocks.id })
.from(workflowBlocks)
.where(
and(eq(workflowBlocks.workflowId, workflowId), eq(workflowBlocks.name, blockNameFilter))
)
if (blocks.length === 0) {
logger.info(
`[${requestId}] No blocks found with name "${blockNameFilter}" for workflow: ${workflowId}`
)
return NextResponse.json(
{
success: true,
data: { memories: [] },
},
{ status: 200 }
)
}
blockIdsToFilter = blocks.map((b) => b.id)
}
if (searchQuery) {
conditions.push(like(memory.key, `%${searchQuery}%`))
@@ -97,27 +153,95 @@ export async function GET(request: NextRequest) {
.orderBy(memory.createdAt)
.limit(limit)
const enrichedMemories = rawMemories.map((mem) => ({
conversationId: mem.key,
data: mem.data,
}))
const filteredMemories = blockIdsToFilter
? rawMemories.filter((mem) => {
const parsed = parseMemoryKey(mem.key)
return parsed && blockIdsToFilter.includes(parsed.blockId)
})
: rawMemories
const blockIds = new Set<string>()
const parsedKeys = new Map<string, { conversationId: string; blockId: string }>()
for (const mem of filteredMemories) {
const parsed = parseMemoryKey(mem.key)
if (parsed) {
blockIds.add(parsed.blockId)
parsedKeys.set(mem.key, parsed)
}
}
const blockNameMap = new Map<string, string>()
if (blockIds.size > 0) {
const blocks = await db
.select({ id: workflowBlocks.id, name: workflowBlocks.name })
.from(workflowBlocks)
.where(
and(
eq(workflowBlocks.workflowId, workflowId),
inArray(workflowBlocks.id, Array.from(blockIds))
)
)
for (const block of blocks) {
blockNameMap.set(block.id, block.name)
}
}
const enrichedMemories = filteredMemories.map((mem) => {
const parsed = parsedKeys.get(mem.key)
if (!parsed) {
return {
conversationId: mem.key,
blockId: 'unknown',
blockName: 'unknown',
data: mem.data,
}
}
const { conversationId, blockId } = parsed
const blockName = blockNameMap.get(blockId) || 'unknown'
return {
conversationId,
blockId,
blockName,
data: mem.data,
}
})
logger.info(
`[${requestId}] Found ${enrichedMemories.length} memories for workspace: ${workspaceId}`
`[${requestId}] Found ${enrichedMemories.length} memories for workflow: ${workflowId}`
)
return NextResponse.json(
{ success: true, data: { memories: enrichedMemories } },
{
success: true,
data: { memories: enrichedMemories },
},
{ status: 200 }
)
} catch (error: any) {
logger.error(`[${requestId}] Error searching memories`, { error })
return NextResponse.json(
{ success: false, error: { message: error.message || 'Failed to search memories' } },
{
success: false,
error: {
message: error.message || 'Failed to search memories',
},
},
{ status: 500 }
)
}
}
/**
* POST handler for creating new memories
* Requires:
* - key: Unique identifier for the memory (within workflow scope)
* - type: Memory type ('agent')
* - data: Memory content (agent message with role and content)
* - workflowId: ID of the workflow this memory belongs to
*/
export async function POST(request: NextRequest) {
const requestId = generateRequestId()
@@ -126,63 +250,123 @@ export async function POST(request: NextRequest) {
if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized memory creation attempt`)
return NextResponse.json(
{ success: false, error: { message: authResult.error || 'Authentication required' } },
{
success: false,
error: {
message: authResult.error || 'Authentication required',
},
},
{ status: 401 }
)
}
logger.info(`[${requestId}] Processing memory creation request`)
const body = await request.json()
const { key, data, workspaceId } = body
const { key, data, workflowId } = body
if (!key) {
logger.warn(`[${requestId}] Missing required field: key`)
return NextResponse.json(
{ success: false, error: { message: 'Memory key is required' } },
{
success: false,
error: {
message: 'Memory key is required',
},
},
{ status: 400 }
)
}
if (!data) {
logger.warn(`[${requestId}] Missing required field: data`)
return NextResponse.json(
{ success: false, error: { message: 'Memory data is required' } },
{
success: false,
error: {
message: 'Memory data is required',
},
},
{ status: 400 }
)
}
if (!workspaceId) {
if (!workflowId) {
logger.warn(`[${requestId}] Missing required field: workflowId`)
return NextResponse.json(
{ success: false, error: { message: 'workspaceId is required' } },
{
success: false,
error: {
message: 'workflowId is required',
},
},
{ status: 400 }
)
}
const { hasAccess, canWrite } = await checkWorkspaceAccess(workspaceId, authResult.userId)
if (!hasAccess) {
const accessContext = await getWorkflowAccessContext(workflowId, authResult.userId)
if (!accessContext) {
logger.warn(`[${requestId}] Workflow ${workflowId} not found for user ${authResult.userId}`)
return NextResponse.json(
{ success: false, error: { message: 'Workspace not found' } },
{
success: false,
error: {
message: 'Workflow not found',
},
},
{ status: 404 }
)
}
if (!canWrite) {
const { workspacePermission, isOwner } = accessContext
const hasWritePermission =
isOwner || workspacePermission === 'write' || workspacePermission === 'admin'
if (!hasWritePermission) {
logger.warn(
`[${requestId}] User ${authResult.userId} denied write access to workflow ${workflowId}`
)
return NextResponse.json(
{ success: false, error: { message: 'Write access denied to this workspace' } },
{
success: false,
error: {
message: 'Write access denied to this workflow',
},
},
{ status: 403 }
)
}
logger.info(
`[${requestId}] User ${authResult.userId} (${authResult.authType}) creating memory for workflow ${workflowId}`
)
const dataToValidate = Array.isArray(data) ? data : [data]
for (const msg of dataToValidate) {
if (!msg || typeof msg !== 'object' || !msg.role || !msg.content) {
logger.warn(`[${requestId}] Missing required message fields`)
return NextResponse.json(
{ success: false, error: { message: 'Memory requires messages with role and content' } },
{
success: false,
error: {
message: 'Memory requires messages with role and content',
},
},
{ status: 400 }
)
}
if (!['user', 'assistant', 'system'].includes(msg.role)) {
logger.warn(`[${requestId}] Invalid message role: ${msg.role}`)
return NextResponse.json(
{ success: false, error: { message: 'Message role must be user, assistant, or system' } },
{
success: false,
error: {
message: 'Message role must be user, assistant, or system',
},
},
{ status: 400 }
)
}
@@ -198,59 +382,114 @@ export async function POST(request: NextRequest) {
.insert(memory)
.values({
id,
workspaceId,
workflowId,
key,
data: initialData,
createdAt: now,
updatedAt: now,
})
.onConflictDoUpdate({
target: [memory.workspaceId, memory.key],
target: [memory.workflowId, memory.key],
set: {
data: sql`${memory.data} || ${JSON.stringify(initialData)}::jsonb`,
updatedAt: now,
},
})
logger.info(`[${requestId}] Memory operation successful: ${key} for workspace: ${workspaceId}`)
logger.info(
`[${requestId}] Memory operation successful (atomic): ${key} for workflow: ${workflowId}`
)
const allMemories = await db
.select()
.from(memory)
.where(
and(eq(memory.key, key), eq(memory.workspaceId, workspaceId), isNull(memory.deletedAt))
)
.where(and(eq(memory.key, key), eq(memory.workflowId, workflowId), isNull(memory.deletedAt)))
.orderBy(memory.createdAt)
if (allMemories.length === 0) {
logger.warn(`[${requestId}] No memories found after creating/updating memory: ${key}`)
return NextResponse.json(
{ success: false, error: { message: 'Failed to retrieve memory after creation/update' } },
{
success: false,
error: {
message: 'Failed to retrieve memory after creation/update',
},
},
{ status: 500 }
)
}
const memoryRecord = allMemories[0]
const parsed = parseMemoryKey(memoryRecord.key)
let enrichedMemory
if (!parsed) {
enrichedMemory = {
conversationId: memoryRecord.key,
blockId: 'unknown',
blockName: 'unknown',
data: memoryRecord.data,
}
} else {
const { conversationId, blockId } = parsed
const blockName = await (async () => {
const blocks = await db
.select({ name: workflowBlocks.name })
.from(workflowBlocks)
.where(and(eq(workflowBlocks.id, blockId), eq(workflowBlocks.workflowId, workflowId)))
.limit(1)
return blocks.length > 0 ? blocks[0].name : 'unknown'
})()
enrichedMemory = {
conversationId,
blockId,
blockName,
data: memoryRecord.data,
}
}
return NextResponse.json(
{ success: true, data: { conversationId: memoryRecord.key, data: memoryRecord.data } },
{
success: true,
data: enrichedMemory,
},
{ status: 200 }
)
} catch (error: any) {
if (error.code === '23505') {
logger.warn(`[${requestId}] Duplicate key violation`)
return NextResponse.json(
{ success: false, error: { message: 'Memory with this key already exists' } },
{
success: false,
error: {
message: 'Memory with this key already exists',
},
},
{ status: 409 }
)
}
logger.error(`[${requestId}] Error creating memory`, { error })
return NextResponse.json(
{ success: false, error: { message: error.message || 'Failed to create memory' } },
{
success: false,
error: {
message: error.message || 'Failed to create memory',
},
},
{ status: 500 }
)
}
}
/**
* DELETE handler for pattern-based memory deletion
* Supports query parameters:
* - workflowId: Required
* - conversationId: Optional - delete all memories for this conversation
* - blockId: Optional - delete all memories for this block
* - blockName: Optional - delete all memories for blocks with this name
*/
export async function DELETE(request: NextRequest) {
const requestId = generateRequestId()
@@ -259,52 +498,175 @@ export async function DELETE(request: NextRequest) {
if (!authResult.success || !authResult.userId) {
logger.warn(`[${requestId}] Unauthorized memory deletion attempt`)
return NextResponse.json(
{ success: false, error: { message: authResult.error || 'Authentication required' } },
{
success: false,
error: {
message: authResult.error || 'Authentication required',
},
},
{ status: 401 }
)
}
logger.info(`[${requestId}] Processing memory deletion request`)
const url = new URL(request.url)
const workspaceId = url.searchParams.get('workspaceId')
const workflowId = url.searchParams.get('workflowId')
const conversationId = url.searchParams.get('conversationId')
const blockId = url.searchParams.get('blockId')
const blockName = url.searchParams.get('blockName')
if (!workspaceId) {
if (!workflowId) {
logger.warn(`[${requestId}] Missing required parameter: workflowId`)
return NextResponse.json(
{ success: false, error: { message: 'workspaceId parameter is required' } },
{
success: false,
error: {
message: 'workflowId parameter is required',
},
},
{ status: 400 }
)
}
if (!conversationId) {
if (!conversationId && !blockId && !blockName) {
logger.warn(`[${requestId}] No filter parameters provided`)
return NextResponse.json(
{ success: false, error: { message: 'conversationId must be provided' } },
{
success: false,
error: {
message: 'At least one of conversationId, blockId, or blockName must be provided',
},
},
{ status: 400 }
)
}
const { hasAccess, canWrite } = await checkWorkspaceAccess(workspaceId, authResult.userId)
if (!hasAccess) {
const accessContext = await getWorkflowAccessContext(workflowId, authResult.userId)
if (!accessContext) {
logger.warn(`[${requestId}] Workflow ${workflowId} not found for user ${authResult.userId}`)
return NextResponse.json(
{ success: false, error: { message: 'Workspace not found' } },
{
success: false,
error: {
message: 'Workflow not found',
},
},
{ status: 404 }
)
}
if (!canWrite) {
const { workspacePermission, isOwner } = accessContext
const hasWritePermission =
isOwner || workspacePermission === 'write' || workspacePermission === 'admin'
if (!hasWritePermission) {
logger.warn(
`[${requestId}] User ${authResult.userId} denied delete access to workflow ${workflowId}`
)
return NextResponse.json(
{ success: false, error: { message: 'Write access denied to this workspace' } },
{
success: false,
error: {
message: 'Write access denied to this workflow',
},
},
{ status: 403 }
)
}
const result = await db
.delete(memory)
.where(and(eq(memory.key, conversationId), eq(memory.workspaceId, workspaceId)))
.returning({ id: memory.id })
logger.info(
`[${requestId}] User ${authResult.userId} (${authResult.authType}) deleting memories for workflow ${workflowId}`
)
const deletedCount = result.length
let deletedCount = 0
logger.info(`[${requestId}] Deleted ${deletedCount} memories for workspace: ${workspaceId}`)
if (conversationId && blockId) {
const key = `${conversationId}:${blockId}`
const result = await db
.delete(memory)
.where(and(eq(memory.key, key), eq(memory.workflowId, workflowId)))
.returning({ id: memory.id })
deletedCount = result.length
} else if (conversationId && blockName) {
const blocks = await db
.select({ id: workflowBlocks.id })
.from(workflowBlocks)
.where(and(eq(workflowBlocks.workflowId, workflowId), eq(workflowBlocks.name, blockName)))
if (blocks.length === 0) {
return NextResponse.json(
{
success: true,
data: {
message: `No blocks found with name "${blockName}"`,
deletedCount: 0,
},
},
{ status: 200 }
)
}
for (const block of blocks) {
const key = `${conversationId}:${block.id}`
const result = await db
.delete(memory)
.where(and(eq(memory.key, key), eq(memory.workflowId, workflowId)))
.returning({ id: memory.id })
deletedCount += result.length
}
} else if (conversationId) {
const pattern = `${conversationId}:%`
const result = await db
.delete(memory)
.where(and(like(memory.key, pattern), eq(memory.workflowId, workflowId)))
.returning({ id: memory.id })
deletedCount = result.length
} else if (blockId) {
const pattern = `%:${blockId}`
const result = await db
.delete(memory)
.where(and(like(memory.key, pattern), eq(memory.workflowId, workflowId)))
.returning({ id: memory.id })
deletedCount = result.length
} else if (blockName) {
const blocks = await db
.select({ id: workflowBlocks.id })
.from(workflowBlocks)
.where(and(eq(workflowBlocks.workflowId, workflowId), eq(workflowBlocks.name, blockName)))
if (blocks.length === 0) {
return NextResponse.json(
{
success: true,
data: {
message: `No blocks found with name "${blockName}"`,
deletedCount: 0,
},
},
{ status: 200 }
)
}
for (const block of blocks) {
const pattern = `%:${block.id}`
const result = await db
.delete(memory)
.where(and(like(memory.key, pattern), eq(memory.workflowId, workflowId)))
.returning({ id: memory.id })
deletedCount += result.length
}
}
logger.info(
`[${requestId}] Successfully deleted ${deletedCount} memories for workflow: ${workflowId}`
)
return NextResponse.json(
{
success: true,
@@ -321,7 +683,12 @@ export async function DELETE(request: NextRequest) {
} catch (error: any) {
logger.error(`[${requestId}] Error deleting memories`, { error })
return NextResponse.json(
{ success: false, error: { message: error.message || 'Failed to delete memories' } },
{
success: false,
error: {
message: error.message || 'Failed to delete memories',
},
},
{ status: 500 }
)
}

View File

@@ -28,7 +28,7 @@ const updateInvitationSchema = z.object({
// Get invitation details
export async function GET(
_request: NextRequest,
_req: NextRequest,
{ params }: { params: Promise<{ id: string; invitationId: string }> }
) {
const { id: organizationId, invitationId } = await params

View File

@@ -6,29 +6,12 @@ const logger = createLogger('OpenRouterModelsAPI')
interface OpenRouterModel {
id: string
context_length?: number
supported_parameters?: string[]
pricing?: {
prompt?: string
completion?: string
}
}
interface OpenRouterResponse {
data: OpenRouterModel[]
}
export interface OpenRouterModelInfo {
id: string
contextLength?: number
supportsStructuredOutputs?: boolean
supportsTools?: boolean
pricing?: {
input: number
output: number
}
}
export async function GET(_request: NextRequest) {
try {
const response = await fetch('https://openrouter.ai/api/v1/models', {
@@ -41,51 +24,23 @@ export async function GET(_request: NextRequest) {
status: response.status,
statusText: response.statusText,
})
return NextResponse.json({ models: [], modelInfo: {} })
return NextResponse.json({ models: [] })
}
const data = (await response.json()) as OpenRouterResponse
const modelInfo: Record<string, OpenRouterModelInfo> = {}
const allModels: string[] = []
for (const model of data.data ?? []) {
const modelId = `openrouter/${model.id}`
allModels.push(modelId)
const supportedParams = model.supported_parameters ?? []
modelInfo[modelId] = {
id: modelId,
contextLength: model.context_length,
supportsStructuredOutputs: supportedParams.includes('structured_outputs'),
supportsTools: supportedParams.includes('tools'),
pricing: model.pricing
? {
input: Number.parseFloat(model.pricing.prompt ?? '0') * 1000000,
output: Number.parseFloat(model.pricing.completion ?? '0') * 1000000,
}
: undefined,
}
}
const uniqueModels = Array.from(new Set(allModels))
const models = filterBlacklistedModels(uniqueModels)
const structuredOutputCount = Object.values(modelInfo).filter(
(m) => m.supportsStructuredOutputs
).length
const allModels = Array.from(new Set(data.data?.map((model) => `openrouter/${model.id}`) ?? []))
const models = filterBlacklistedModels(allModels)
logger.info('Successfully fetched OpenRouter models', {
count: models.length,
filtered: uniqueModels.length - models.length,
withStructuredOutputs: structuredOutputCount,
filtered: allModels.length - models.length,
})
return NextResponse.json({ models, modelInfo })
return NextResponse.json({ models })
} catch (error) {
logger.error('Error fetching OpenRouter models', {
error: error instanceof Error ? error.message : 'Unknown error',
})
return NextResponse.json({ models: [], modelInfo: {} })
return NextResponse.json({ models: [] })
}
}

View File

@@ -1,19 +1,16 @@
import { db } from '@sim/db'
import { templates } from '@sim/db/schema'
import { templates, user } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { verifySuperUser } from '@/lib/templates/permissions'
const logger = createLogger('TemplateApprovalAPI')
export const revalidate = 0
/**
* POST /api/templates/[id]/approve - Approve a template (super users only)
*/
// POST /api/templates/[id]/approve - Approve a template (super users only)
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -25,18 +22,23 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { isSuperUser } = await verifySuperUser(session.user.id)
if (!isSuperUser) {
// Check if user is a super user
const currentUser = await db.select().from(user).where(eq(user.id, session.user.id)).limit(1)
if (!currentUser[0]?.isSuperUser) {
logger.warn(`[${requestId}] Non-super user attempted to approve template: ${id}`)
return NextResponse.json({ error: 'Only super users can approve templates' }, { status: 403 })
}
// Check if template exists
const existingTemplate = await db.select().from(templates).where(eq(templates.id, id)).limit(1)
if (existingTemplate.length === 0) {
logger.warn(`[${requestId}] Template not found for approval: ${id}`)
return NextResponse.json({ error: 'Template not found' }, { status: 404 })
}
// Update template status to approved
await db
.update(templates)
.set({ status: 'approved', updatedAt: new Date() })
@@ -54,11 +56,9 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
}
/**
* DELETE /api/templates/[id]/approve - Unapprove a template (super users only)
*/
// POST /api/templates/[id]/reject - Reject a template (super users only)
export async function DELETE(
_request: NextRequest,
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()
@@ -71,18 +71,23 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { isSuperUser } = await verifySuperUser(session.user.id)
if (!isSuperUser) {
// Check if user is a super user
const currentUser = await db.select().from(user).where(eq(user.id, session.user.id)).limit(1)
if (!currentUser[0]?.isSuperUser) {
logger.warn(`[${requestId}] Non-super user attempted to reject template: ${id}`)
return NextResponse.json({ error: 'Only super users can reject templates' }, { status: 403 })
}
// Check if template exists
const existingTemplate = await db.select().from(templates).where(eq(templates.id, id)).limit(1)
if (existingTemplate.length === 0) {
logger.warn(`[${requestId}] Template not found for rejection: ${id}`)
return NextResponse.json({ error: 'Template not found' }, { status: 404 })
}
// Update template status to rejected
await db
.update(templates)
.set({ status: 'rejected', updatedAt: new Date() })

View File

@@ -1,142 +0,0 @@
import { db } from '@sim/db'
import { templates } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { createLogger } from '@/lib/logs/console/logger'
import { verifyTemplateOwnership } from '@/lib/templates/permissions'
import { uploadFile } from '@/lib/uploads/core/storage-service'
import { isValidPng } from '@/lib/uploads/utils/validation'
const logger = createLogger('TemplateOGImageAPI')
/**
* PUT /api/templates/[id]/og-image
* Upload a pre-generated OG image for a template.
* Accepts base64-encoded image data in the request body.
*/
export async function PUT(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
try {
const session = await getSession()
if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthorized OG image upload attempt for template: ${id}`)
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { authorized, error, status } = await verifyTemplateOwnership(
id,
session.user.id,
'admin'
)
if (!authorized) {
logger.warn(`[${requestId}] User denied permission to upload OG image for template ${id}`)
return NextResponse.json({ error }, { status: status || 403 })
}
const body = await request.json()
const { imageData } = body
if (!imageData || typeof imageData !== 'string') {
return NextResponse.json(
{ error: 'Missing or invalid imageData (expected base64 string)' },
{ status: 400 }
)
}
const base64Data = imageData.includes(',') ? imageData.split(',')[1] : imageData
const imageBuffer = Buffer.from(base64Data, 'base64')
if (!isValidPng(imageBuffer)) {
return NextResponse.json({ error: 'Invalid PNG image data' }, { status: 400 })
}
const maxSize = 5 * 1024 * 1024
if (imageBuffer.length > maxSize) {
return NextResponse.json({ error: 'Image too large. Maximum size is 5MB.' }, { status: 400 })
}
const timestamp = Date.now()
const storageKey = `og-images/templates/${id}/${timestamp}.png`
logger.info(`[${requestId}] Uploading OG image for template ${id}: ${storageKey}`)
const uploadResult = await uploadFile({
file: imageBuffer,
fileName: storageKey,
contentType: 'image/png',
context: 'og-images',
preserveKey: true,
customKey: storageKey,
})
const baseUrl = getBaseUrl()
const ogImageUrl = `${baseUrl}${uploadResult.path}?context=og-images`
await db
.update(templates)
.set({
ogImageUrl,
updatedAt: new Date(),
})
.where(eq(templates.id, id))
logger.info(`[${requestId}] Successfully uploaded OG image for template ${id}: ${ogImageUrl}`)
return NextResponse.json({
success: true,
ogImageUrl,
})
} catch (error: unknown) {
logger.error(`[${requestId}] Error uploading OG image for template ${id}:`, error)
return NextResponse.json({ error: 'Failed to upload OG image' }, { status: 500 })
}
}
/**
* DELETE /api/templates/[id]/og-image
* Remove the OG image for a template.
*/
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const requestId = generateRequestId()
const { id } = await params
try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { authorized, error, status } = await verifyTemplateOwnership(
id,
session.user.id,
'admin'
)
if (!authorized) {
logger.warn(`[${requestId}] User denied permission to delete OG image for template ${id}`)
return NextResponse.json({ error }, { status: status || 403 })
}
await db
.update(templates)
.set({
ogImageUrl: null,
updatedAt: new Date(),
})
.where(eq(templates.id, id))
logger.info(`[${requestId}] Removed OG image for template ${id}`)
return NextResponse.json({ success: true })
} catch (error: unknown) {
logger.error(`[${requestId}] Error removing OG image for template ${id}:`, error)
return NextResponse.json({ error: 'Failed to remove OG image' }, { status: 500 })
}
}

View File

@@ -1,19 +1,16 @@
import { db } from '@sim/db'
import { templates } from '@sim/db/schema'
import { templates, user } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { verifySuperUser } from '@/lib/templates/permissions'
const logger = createLogger('TemplateRejectionAPI')
export const revalidate = 0
/**
* POST /api/templates/[id]/reject - Reject a template (super users only)
*/
// POST /api/templates/[id]/reject - Reject a template (super users only)
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -25,18 +22,23 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { isSuperUser } = await verifySuperUser(session.user.id)
if (!isSuperUser) {
// Check if user is a super user
const currentUser = await db.select().from(user).where(eq(user.id, session.user.id)).limit(1)
if (!currentUser[0]?.isSuperUser) {
logger.warn(`[${requestId}] Non-super user attempted to reject template: ${id}`)
return NextResponse.json({ error: 'Only super users can reject templates' }, { status: 403 })
}
// Check if template exists
const existingTemplate = await db.select().from(templates).where(eq(templates.id, id)).limit(1)
if (existingTemplate.length === 0) {
logger.warn(`[${requestId}] Template not found for rejection: ${id}`)
return NextResponse.json({ error: 'Template not found' }, { status: 404 })
}
// Update template status to rejected
await db
.update(templates)
.set({ status: 'rejected', updatedAt: new Date() })

View File

@@ -1,6 +1,6 @@
import { db } from '@sim/db'
import { templateCreators, templates, workflow } from '@sim/db/schema'
import { eq, sql } from 'drizzle-orm'
import { member, templateCreators, templates, workflow } from '@sim/db/schema'
import { and, eq, or, sql } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
@@ -15,6 +15,7 @@ const logger = createLogger('TemplateByIdAPI')
export const revalidate = 0
// GET /api/templates/[id] - Retrieve a single template by ID
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const requestId = generateRequestId()
const { id } = await params
@@ -24,6 +25,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
logger.debug(`[${requestId}] Fetching template: ${id}`)
// Fetch the template by ID with creator info
const result = await db
.select({
template: templates,
@@ -45,10 +47,12 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
creator: creator || undefined,
}
// Only show approved templates to non-authenticated users
if (!session?.user?.id && template.status !== 'approved') {
return NextResponse.json({ error: 'Template not found' }, { status: 404 })
}
// Check if user has starred (only if authenticated)
let isStarred = false
if (session?.user?.id) {
const { templateStars } = await import('@sim/db/schema')
@@ -76,6 +80,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
logger.debug(`[${requestId}] Incremented view count for template: ${id}`)
} catch (viewError) {
// Log the error but don't fail the request
logger.warn(`[${requestId}] Failed to increment view count for template: ${id}`, viewError)
}
}
@@ -133,6 +138,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
const { name, details, creatorId, tags, updateState } = validationResult.data
// Check if template exists
const existingTemplate = await db.select().from(templates).where(eq(templates.id, id)).limit(1)
if (existingTemplate.length === 0) {
@@ -140,54 +146,32 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: 'Template not found' }, { status: 404 })
}
const template = existingTemplate[0]
if (!template.creatorId) {
logger.warn(`[${requestId}] Template ${id} has no creator, denying update`)
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}
const { verifyCreatorPermission } = await import('@/lib/templates/permissions')
const { hasPermission, error: permissionError } = await verifyCreatorPermission(
session.user.id,
template.creatorId,
'admin'
)
if (!hasPermission) {
logger.warn(`[${requestId}] User denied permission to update template ${id}`)
return NextResponse.json({ error: permissionError || 'Access denied' }, { status: 403 })
}
// No permission check needed - template updates only happen from within the workspace
// where the user is already editing the connected workflow
// Prepare update data - only include fields that were provided
const updateData: any = {
updatedAt: new Date(),
}
// Only update fields that were provided
if (name !== undefined) updateData.name = name
if (details !== undefined) updateData.details = details
if (tags !== undefined) updateData.tags = tags
if (creatorId !== undefined) updateData.creatorId = creatorId
if (updateState && template.workflowId) {
const { verifyWorkflowAccess } = await import('@/socket-server/middleware/permissions')
const { hasAccess: hasWorkflowAccess } = await verifyWorkflowAccess(
session.user.id,
template.workflowId
)
if (!hasWorkflowAccess) {
logger.warn(`[${requestId}] User denied workflow access for state sync on template ${id}`)
return NextResponse.json({ error: 'Access denied to workflow' }, { status: 403 })
}
// Only update the state if explicitly requested and the template has a connected workflow
if (updateState && existingTemplate[0].workflowId) {
// Load the current workflow state from normalized tables
const { loadWorkflowFromNormalizedTables } = await import('@/lib/workflows/persistence/utils')
const normalizedData = await loadWorkflowFromNormalizedTables(template.workflowId)
const normalizedData = await loadWorkflowFromNormalizedTables(existingTemplate[0].workflowId)
if (normalizedData) {
// Also fetch workflow variables
const [workflowRecord] = await db
.select({ variables: workflow.variables })
.from(workflow)
.where(eq(workflow.id, template.workflowId))
.where(eq(workflow.id, existingTemplate[0].workflowId))
.limit(1)
const currentState = {
@@ -199,15 +183,17 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
lastSaved: Date.now(),
}
// Extract credential requirements from the new state
const requiredCredentials = extractRequiredCredentials(currentState)
// Sanitize the state before storing
const sanitizedState = sanitizeCredentials(currentState)
updateData.state = sanitizedState
updateData.requiredCredentials = requiredCredentials
logger.info(
`[${requestId}] Updating template state and credentials from current workflow: ${template.workflowId}`
`[${requestId}] Updating template state and credentials from current workflow: ${existingTemplate[0].workflowId}`
)
} else {
logger.warn(`[${requestId}] Could not load workflow state for template: ${id}`)
@@ -247,6 +233,7 @@ export async function DELETE(
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Fetch template
const existing = await db.select().from(templates).where(eq(templates.id, id)).limit(1)
if (existing.length === 0) {
logger.warn(`[${requestId}] Template not found for delete: ${id}`)
@@ -255,21 +242,41 @@ export async function DELETE(
const template = existing[0]
if (!template.creatorId) {
logger.warn(`[${requestId}] Template ${id} has no creator, denying delete`)
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}
// Permission: Only admin/owner of creator profile can delete
if (template.creatorId) {
const creatorProfile = await db
.select()
.from(templateCreators)
.where(eq(templateCreators.id, template.creatorId))
.limit(1)
const { verifyCreatorPermission } = await import('@/lib/templates/permissions')
const { hasPermission, error: permissionError } = await verifyCreatorPermission(
session.user.id,
template.creatorId,
'admin'
)
if (creatorProfile.length > 0) {
const creator = creatorProfile[0]
let hasPermission = false
if (!hasPermission) {
logger.warn(`[${requestId}] User denied permission to delete template ${id}`)
return NextResponse.json({ error: permissionError || 'Access denied' }, { status: 403 })
if (creator.referenceType === 'user') {
hasPermission = creator.referenceId === session.user.id
} else if (creator.referenceType === 'organization') {
// For delete, require admin/owner role
const membership = await db
.select()
.from(member)
.where(
and(
eq(member.userId, session.user.id),
eq(member.organizationId, creator.referenceId),
or(eq(member.role, 'admin'), eq(member.role, 'owner'))
)
)
.limit(1)
hasPermission = membership.length > 0
}
if (!hasPermission) {
logger.warn(`[${requestId}] User denied permission to delete template ${id}`)
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}
}
}
await db.delete(templates).where(eq(templates.id, id))

View File

@@ -1,5 +1,6 @@
import { db } from '@sim/db'
import {
member,
templateCreators,
templateStars,
templates,
@@ -203,18 +204,51 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Workflow not found' }, { status: 404 })
}
const { verifyCreatorPermission } = await import('@/lib/templates/permissions')
const { hasPermission, error: permissionError } = await verifyCreatorPermission(
session.user.id,
data.creatorId,
'member'
)
// Validate creator profile - required for all templates
const creatorProfile = await db
.select()
.from(templateCreators)
.where(eq(templateCreators.id, data.creatorId))
.limit(1)
if (!hasPermission) {
logger.warn(`[${requestId}] User cannot use creator profile: ${data.creatorId}`)
return NextResponse.json({ error: permissionError || 'Access denied' }, { status: 403 })
if (creatorProfile.length === 0) {
logger.warn(`[${requestId}] Creator profile not found: ${data.creatorId}`)
return NextResponse.json({ error: 'Creator profile not found' }, { status: 404 })
}
const creator = creatorProfile[0]
// Verify user has permission to use this creator profile
if (creator.referenceType === 'user') {
if (creator.referenceId !== session.user.id) {
logger.warn(`[${requestId}] User cannot use creator profile: ${data.creatorId}`)
return NextResponse.json(
{ error: 'You do not have permission to use this creator profile' },
{ status: 403 }
)
}
} else if (creator.referenceType === 'organization') {
// Verify user is a member of the organization
const membership = await db
.select()
.from(member)
.where(
and(eq(member.userId, session.user.id), eq(member.organizationId, creator.referenceId))
)
.limit(1)
if (membership.length === 0) {
logger.warn(
`[${requestId}] User not a member of organization for creator: ${data.creatorId}`
)
return NextResponse.json(
{ error: 'You must be a member of the organization to use its creator profile' },
{ status: 403 }
)
}
}
// Create the template
const templateId = uuidv4()
const now = new Date()

View File

@@ -1,7 +1,6 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
@@ -109,14 +108,6 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
}
if (folderId) {
const folderIdValidation = validateAlphanumericId(folderId, 'folderId', 50)
if (!folderIdValidation.isValid) {
logger.warn(`[${requestId}] Invalid folderId`, { error: folderIdValidation.error })
return NextResponse.json({ error: folderIdValidation.error }, { status: 400 })
}
}
const qParts: string[] = ['trashed = false']
if (folderId) {
qParts.push(`'${escapeForDriveQuery(folderId)}' in parents`)

View File

@@ -1,7 +1,6 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
@@ -51,29 +50,6 @@ export async function POST(request: NextRequest) {
.map((id) => id.trim())
.filter((id) => id.length > 0)
for (const labelId of labelIds) {
const labelIdValidation = validateAlphanumericId(labelId, 'labelId', 255)
if (!labelIdValidation.isValid) {
logger.warn(`[${requestId}] Invalid label ID: ${labelIdValidation.error}`)
return NextResponse.json(
{
success: false,
error: labelIdValidation.error,
},
{ status: 400 }
)
}
}
const messageIdValidation = validateAlphanumericId(validatedData.messageId, 'messageId', 255)
if (!messageIdValidation.isValid) {
logger.warn(`[${requestId}] Invalid message ID: ${messageIdValidation.error}`)
return NextResponse.json(
{ success: false, error: messageIdValidation.error },
{ status: 400 }
)
}
const gmailResponse = await fetch(
`${GMAIL_API_BASE}/messages/${validatedData.messageId}/modify`,
{

View File

@@ -3,7 +3,6 @@ import { account } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
@@ -39,12 +38,6 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
}
const credentialIdValidation = validateAlphanumericId(credentialId, 'credentialId', 255)
if (!credentialIdValidation.isValid) {
logger.warn(`[${requestId}] Invalid credential ID: ${credentialIdValidation.error}`)
return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 })
}
let credentials = await db
.select()
.from(account)

View File

@@ -1,7 +1,6 @@
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkHybridAuth } from '@/lib/auth/hybrid'
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
@@ -54,29 +53,6 @@ export async function POST(request: NextRequest) {
.map((id) => id.trim())
.filter((id) => id.length > 0)
for (const labelId of labelIds) {
const labelIdValidation = validateAlphanumericId(labelId, 'labelId', 255)
if (!labelIdValidation.isValid) {
logger.warn(`[${requestId}] Invalid label ID: ${labelIdValidation.error}`)
return NextResponse.json(
{
success: false,
error: labelIdValidation.error,
},
{ status: 400 }
)
}
}
const messageIdValidation = validateAlphanumericId(validatedData.messageId, 'messageId', 255)
if (!messageIdValidation.isValid) {
logger.warn(`[${requestId}] Invalid message ID: ${messageIdValidation.error}`)
return NextResponse.json(
{ success: false, error: messageIdValidation.error },
{ status: 400 }
)
}
const gmailResponse = await fetch(
`${GMAIL_API_BASE}/messages/${validatedData.messageId}/modify`,
{

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