mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-09 23:17:59 -05:00
Compare commits
39 Commits
fix/slack-
...
v0.5.23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7bbef8620 | ||
|
|
6fd4087a79 | ||
|
|
cb6e763714 | ||
|
|
d06b360b1d | ||
|
|
0713580862 | ||
|
|
f421f27d3f | ||
|
|
0083c89fa5 | ||
|
|
3cec449402 | ||
|
|
c5b3fcb181 | ||
|
|
dd7db6e144 | ||
|
|
306043eedb | ||
|
|
569598b39e | ||
|
|
cc66aa5a3e | ||
|
|
aea32d423f | ||
|
|
22abf98835 | ||
|
|
52edbea659 | ||
|
|
aa1d896b38 | ||
|
|
2fcd07e82d | ||
|
|
0db5ba1b27 | ||
|
|
e390ba0491 | ||
|
|
2f0509adaf | ||
|
|
9f0584a818 | ||
|
|
d480057fd3 | ||
|
|
c27c233da0 | ||
|
|
ebef5f3a27 | ||
|
|
12c4c2d44f | ||
|
|
929a352edb | ||
|
|
6cd078b0fe | ||
|
|
31874939ee | ||
|
|
e157ce5fbc | ||
|
|
774e5d585c | ||
|
|
54cc93743f | ||
|
|
8c32ad4c0d | ||
|
|
1d08796853 | ||
|
|
ebcd243942 | ||
|
|
b7e814b721 | ||
|
|
842ef27ed9 | ||
|
|
31c34b2ea3 | ||
|
|
8f0ef58056 |
45
.cursor/rules/emcn-components.mdc
Normal file
45
.cursor/rules/emcn-components.mdc
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
description: EMCN component library patterns with CVA
|
||||
globs: ["apps/sim/components/emcn/**"]
|
||||
---
|
||||
|
||||
# EMCN Component Guidelines
|
||||
|
||||
## When to Use CVA vs Direct Styles
|
||||
|
||||
**Use CVA (class-variance-authority) when:**
|
||||
- 2+ visual variants (primary, secondary, outline)
|
||||
- Multiple sizes or state variations
|
||||
- Example: Button with variants
|
||||
|
||||
**Use direct className when:**
|
||||
- Single consistent style
|
||||
- No variations needed
|
||||
- Example: Label with one style
|
||||
|
||||
## Patterns
|
||||
|
||||
**With CVA:**
|
||||
```tsx
|
||||
const buttonVariants = cva('base-classes', {
|
||||
variants: {
|
||||
variant: { default: '...', primary: '...' },
|
||||
size: { sm: '...', md: '...' }
|
||||
}
|
||||
})
|
||||
export { Button, buttonVariants }
|
||||
```
|
||||
|
||||
**Without CVA:**
|
||||
```tsx
|
||||
function Label({ className, ...props }) {
|
||||
return <Primitive className={cn('single-style-classes', className)} {...props} />
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
- Use Radix UI primitives for accessibility
|
||||
- Export component and variants (if using CVA)
|
||||
- TSDoc with usage examples
|
||||
- Consistent tokens: `font-medium`, `text-[12px]`, `rounded-[4px]`
|
||||
- Always use `transition-colors` for hover states
|
||||
20
.cursor/rules/global.mdc
Normal file
20
.cursor/rules/global.mdc
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
description: Global coding standards that apply to all files
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Global Standards
|
||||
|
||||
You are a professional software engineer. All code must follow best practices: accurate, readable, clean, and efficient.
|
||||
|
||||
## Logging
|
||||
Use `logger.info`, `logger.warn`, `logger.error` instead of `console.log`.
|
||||
|
||||
## Comments
|
||||
Use TSDoc for documentation. No `====` separators. No non-TSDoc comments.
|
||||
|
||||
## Styling
|
||||
Never update global styles. Keep all styling local to components.
|
||||
|
||||
## Package Manager
|
||||
Use `bun` and `bunx`, not `npm` and `npx`.
|
||||
67
.cursor/rules/sim-architecture.mdc
Normal file
67
.cursor/rules/sim-architecture.mdc
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
description: Core architecture principles for the Sim app
|
||||
globs: ["apps/sim/**"]
|
||||
---
|
||||
|
||||
# Sim App Architecture
|
||||
|
||||
## Core Principles
|
||||
1. **Single Responsibility**: Each component, hook, store has one clear purpose
|
||||
2. **Composition Over Complexity**: Break down complex logic into smaller pieces
|
||||
3. **Type Safety First**: TypeScript interfaces for all props, state, return types
|
||||
4. **Predictable State**: Zustand for global state, useState for UI-only concerns
|
||||
5. **Performance by Default**: useMemo, useCallback, refs appropriately
|
||||
|
||||
## File Organization
|
||||
|
||||
```
|
||||
feature/
|
||||
├── components/ # Feature components
|
||||
│ └── sub-feature/ # Sub-feature with own components
|
||||
├── hooks/ # Custom hooks
|
||||
└── feature.tsx # Main component
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
- **Components**: PascalCase (`WorkflowList`, `TriggerPanel`)
|
||||
- **Hooks**: camelCase with `use` prefix (`useWorkflowOperations`)
|
||||
- **Files**: kebab-case matching export (`workflow-list.tsx`)
|
||||
- **Stores**: kebab-case in stores/ (`sidebar/store.ts`)
|
||||
- **Constants**: SCREAMING_SNAKE_CASE
|
||||
- **Interfaces**: PascalCase with suffix (`WorkflowListProps`)
|
||||
|
||||
## State Management
|
||||
|
||||
**useState**: UI-only concerns (dropdown open, hover, form inputs)
|
||||
**Zustand**: Shared state, persistence, global app state
|
||||
**useRef**: DOM refs, avoiding dependency issues, mutable non-reactive values
|
||||
|
||||
## Component Extraction
|
||||
|
||||
**Extract to separate file when:**
|
||||
- Complex (50+ lines)
|
||||
- Used across 2+ files
|
||||
- Has own state/logic
|
||||
|
||||
**Keep inline when:**
|
||||
- Simple (< 10 lines)
|
||||
- Used in only 1 file
|
||||
- Purely presentational
|
||||
|
||||
**Never import utilities from another component file.** Extract shared helpers to `lib/` or `utils/`.
|
||||
|
||||
## Utils Files
|
||||
|
||||
**Never create a `utils.ts` file for a single consumer.** Inline the logic directly in the consuming component.
|
||||
|
||||
**Create `utils.ts` when:**
|
||||
- 2+ files import the same helper
|
||||
|
||||
**Prefer existing sources of truth:**
|
||||
- Before duplicating logic, check if a centralized helper already exists (e.g., `lib/logs/get-trigger-options.ts`)
|
||||
- Import from the source of truth rather than creating wrapper functions
|
||||
|
||||
**Location hierarchy:**
|
||||
- `lib/` — App-wide utilities (auth, billing, core)
|
||||
- `feature/utils.ts` — Feature-scoped utilities (used by 2+ components in the feature)
|
||||
- Inline — Single-use helpers (define directly in the component)
|
||||
64
.cursor/rules/sim-components.mdc
Normal file
64
.cursor/rules/sim-components.mdc
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
description: Component patterns and structure for React components
|
||||
globs: ["apps/sim/**/*.tsx"]
|
||||
---
|
||||
|
||||
# Component Patterns
|
||||
|
||||
## Structure Order
|
||||
```typescript
|
||||
'use client' // Only if using hooks
|
||||
|
||||
// 1. Imports (external → internal → relative)
|
||||
// 2. Constants at module level
|
||||
const CONFIG = { SPACING: 8 } as const
|
||||
|
||||
// 3. Props interface with TSDoc
|
||||
interface ComponentProps {
|
||||
/** Description */
|
||||
requiredProp: string
|
||||
optionalProp?: boolean
|
||||
}
|
||||
|
||||
// 4. Component with TSDoc
|
||||
export function Component({ requiredProp, optionalProp = false }: ComponentProps) {
|
||||
// a. Refs
|
||||
// b. External hooks (useParams, useRouter)
|
||||
// c. Store hooks
|
||||
// d. Custom hooks
|
||||
// e. Local state
|
||||
// f. useMemo computations
|
||||
// g. useCallback handlers
|
||||
// h. useEffect
|
||||
// i. Return JSX
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
1. Add `'use client'` when using React hooks
|
||||
2. Always define props interface
|
||||
3. TSDoc on component: description, @param, @returns
|
||||
4. Extract constants with `as const`
|
||||
5. Use Tailwind only, no inline styles
|
||||
6. Semantic HTML (`aside`, `nav`, `article`)
|
||||
7. Include ARIA attributes where appropriate
|
||||
8. Optional chain callbacks: `onAction?.(id)`
|
||||
|
||||
## Factory Pattern with Caching
|
||||
|
||||
When generating components for a specific signature (e.g., icons):
|
||||
|
||||
```typescript
|
||||
const cache = new Map<string, React.ComponentType<{ className?: string }>>()
|
||||
|
||||
function getColorIcon(color: string) {
|
||||
if (cache.has(color)) return cache.get(color)!
|
||||
|
||||
const Icon = ({ className }: { className?: string }) => (
|
||||
<div className={cn(className, 'rounded-[3px]')} style={{ backgroundColor: color, width: 10, height: 10 }} />
|
||||
)
|
||||
Icon.displayName = `ColorIcon(${color})`
|
||||
cache.set(color, Icon)
|
||||
return Icon
|
||||
}
|
||||
```
|
||||
68
.cursor/rules/sim-hooks.mdc
Normal file
68
.cursor/rules/sim-hooks.mdc
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
description: Custom hook patterns and best practices
|
||||
globs: ["apps/sim/**/use-*.ts", "apps/sim/**/hooks/**/*.ts"]
|
||||
---
|
||||
|
||||
# Hook Patterns
|
||||
|
||||
## Structure
|
||||
```typescript
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('useFeatureName')
|
||||
|
||||
interface UseFeatureProps {
|
||||
id: string
|
||||
onSuccess?: (result: Result) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook description.
|
||||
* @param props - Configuration
|
||||
* @returns State and operations
|
||||
*/
|
||||
export function useFeature({ id, onSuccess }: UseFeatureProps) {
|
||||
// 1. Refs for stable dependencies
|
||||
const idRef = useRef(id)
|
||||
const onSuccessRef = useRef(onSuccess)
|
||||
|
||||
// 2. State
|
||||
const [data, setData] = useState<Data | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
// 3. Sync refs
|
||||
useEffect(() => {
|
||||
idRef.current = id
|
||||
onSuccessRef.current = onSuccess
|
||||
}, [id, onSuccess])
|
||||
|
||||
// 4. Operations with useCallback
|
||||
const fetchData = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const result = await fetch(`/api/${idRef.current}`).then(r => r.json())
|
||||
setData(result)
|
||||
onSuccessRef.current?.(result)
|
||||
} catch (err) {
|
||||
setError(err as Error)
|
||||
logger.error('Failed', { error: err })
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, []) // Empty deps - using refs
|
||||
|
||||
// 5. Return grouped by state/operations
|
||||
return { data, isLoading, error, fetchData }
|
||||
}
|
||||
```
|
||||
|
||||
## Rules
|
||||
1. Single responsibility per hook
|
||||
2. Props interface required
|
||||
3. TSDoc required
|
||||
4. Use logger, not console.log
|
||||
5. Refs for stable callback dependencies
|
||||
6. Wrap returned functions in useCallback
|
||||
7. Always try/catch async operations
|
||||
8. Track loading/error states
|
||||
37
.cursor/rules/sim-imports.mdc
Normal file
37
.cursor/rules/sim-imports.mdc
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
description: Import patterns for the Sim application
|
||||
globs: ["apps/sim/**/*.ts", "apps/sim/**/*.tsx"]
|
||||
---
|
||||
|
||||
# Import Patterns
|
||||
|
||||
## EMCN Components
|
||||
Import from `@/components/emcn`, never from subpaths like `@/components/emcn/components/modal/modal`.
|
||||
|
||||
**Exception**: CSS imports use actual file paths: `import '@/components/emcn/components/code/code.css'`
|
||||
|
||||
## Feature Components
|
||||
Import from central folder indexes, not specific subfolders:
|
||||
```typescript
|
||||
// ✅ Correct
|
||||
import { Dashboard, Sidebar } from '@/app/workspace/[workspaceId]/logs/components'
|
||||
|
||||
// ❌ Wrong
|
||||
import { Dashboard } from '@/app/workspace/[workspaceId]/logs/components/dashboard'
|
||||
```
|
||||
|
||||
## Internal vs External
|
||||
- **Cross-feature**: Absolute paths through central index
|
||||
- **Within feature**: Relative paths (`./components/...`, `../utils`)
|
||||
|
||||
## Import Order
|
||||
1. React/core libraries
|
||||
2. External libraries
|
||||
3. UI components (`@/components/emcn`, `@/components/ui`)
|
||||
4. Utilities (`@/lib/...`)
|
||||
5. Feature imports from indexes
|
||||
6. Relative imports
|
||||
7. CSS imports
|
||||
|
||||
## Types
|
||||
Use `type` keyword: `import type { WorkflowLog } from '...'`
|
||||
57
.cursor/rules/sim-stores.mdc
Normal file
57
.cursor/rules/sim-stores.mdc
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
description: Zustand store patterns
|
||||
globs: ["apps/sim/**/store.ts", "apps/sim/**/stores/**/*.ts"]
|
||||
---
|
||||
|
||||
# Zustand Store Patterns
|
||||
|
||||
## Structure
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
interface FeatureState {
|
||||
// State
|
||||
items: Item[]
|
||||
activeId: string | null
|
||||
|
||||
// Actions
|
||||
setItems: (items: Item[]) => void
|
||||
addItem: (item: Item) => void
|
||||
clearState: () => void
|
||||
}
|
||||
|
||||
const createInitialState = () => ({
|
||||
items: [],
|
||||
activeId: null,
|
||||
})
|
||||
|
||||
export const useFeatureStore = create<FeatureState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
...createInitialState(),
|
||||
|
||||
setItems: (items) => set({ items }),
|
||||
|
||||
addItem: (item) => set((state) => ({
|
||||
items: [...state.items, item],
|
||||
})),
|
||||
|
||||
clearState: () => set(createInitialState()),
|
||||
}),
|
||||
{
|
||||
name: 'feature-state',
|
||||
partialize: (state) => ({ items: state.items }),
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Rules
|
||||
1. Interface includes state and actions
|
||||
2. Extract config to module constants
|
||||
3. TSDoc on store
|
||||
4. Only persist what's needed
|
||||
5. Immutable updates only - never mutate
|
||||
6. Use `set((state) => ...)` when depending on previous state
|
||||
7. Provide clear/reset actions
|
||||
47
.cursor/rules/sim-styling.mdc
Normal file
47
.cursor/rules/sim-styling.mdc
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
description: Tailwind CSS and styling conventions
|
||||
globs: ["apps/sim/**/*.tsx", "apps/sim/**/*.css"]
|
||||
---
|
||||
|
||||
# Styling Rules
|
||||
|
||||
## Tailwind
|
||||
1. **No inline styles** - Use Tailwind classes exclusively
|
||||
2. **No duplicate dark classes** - Don't add `dark:` when value matches light mode
|
||||
3. **Exact values** - Use design system values (`text-[14px]`, `h-[25px]`)
|
||||
4. **Prefer px** - Use `px-[4px]` over `px-1`
|
||||
5. **Transitions** - Add `transition-colors` for interactive states
|
||||
|
||||
## Conditional Classes
|
||||
```typescript
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
<div className={cn(
|
||||
'base-classes',
|
||||
isActive && 'active-classes',
|
||||
disabled ? 'opacity-60' : 'hover:bg-accent'
|
||||
)} />
|
||||
```
|
||||
|
||||
## CSS Variables for Dynamic Styles
|
||||
```typescript
|
||||
// In store setter
|
||||
setSidebarWidth: (width) => {
|
||||
set({ sidebarWidth: width })
|
||||
document.documentElement.style.setProperty('--sidebar-width', `${width}px`)
|
||||
}
|
||||
|
||||
// In component
|
||||
<aside style={{ width: 'var(--sidebar-width)' }} />
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
```typescript
|
||||
// ❌ Bad
|
||||
<div style={{ width: 200 }}>
|
||||
<div className='text-[var(--text-primary)] dark:text-[var(--text-primary)]'>
|
||||
|
||||
// ✅ Good
|
||||
<div className='w-[200px]'>
|
||||
<div className='text-[var(--text-primary)]'>
|
||||
```
|
||||
24
.cursor/rules/sim-typescript.mdc
Normal file
24
.cursor/rules/sim-typescript.mdc
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
description: TypeScript conventions and type safety
|
||||
globs: ["apps/sim/**/*.ts", "apps/sim/**/*.tsx"]
|
||||
---
|
||||
|
||||
# TypeScript Rules
|
||||
|
||||
1. **No `any`** - Use proper types or `unknown` with type guards
|
||||
2. **Props interface** - Always define, even for simple components
|
||||
3. **Callback types** - Full signature with params and return type
|
||||
4. **Generics** - Use for reusable components/hooks
|
||||
5. **Const assertions** - `as const` for constant objects/arrays
|
||||
6. **Ref types** - Explicit: `useRef<HTMLDivElement>(null)`
|
||||
|
||||
## Anti-Patterns
|
||||
```typescript
|
||||
// ❌ Bad
|
||||
const handleClick = (e: any) => {}
|
||||
useEffect(() => { doSomething(prop) }, []) // Missing dep
|
||||
|
||||
// ✅ Good
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {}
|
||||
useEffect(() => { doSomething(prop) }, [prop])
|
||||
```
|
||||
19
.cursorrules
19
.cursorrules
@@ -1,19 +0,0 @@
|
||||
# Role
|
||||
|
||||
You are a professional software engineer. All code you write MUST follow best practices, ensuring accuracy, quality, readability, and cleanliness. You MUST make FOCUSED EDITS that are EFFICIENT and ELEGANT.
|
||||
|
||||
## Logs
|
||||
|
||||
ENSURE that you use the logger.info and logger.warn and logger.error instead of the console.log whenever you want to display logs.
|
||||
|
||||
## Comments
|
||||
|
||||
You must use TSDOC for comments. Do not use ==== for comments to separate sections. Do not leave any comments that are not TSDOC.
|
||||
|
||||
## Globals styles
|
||||
|
||||
You should not update the global styles unless it is absolutely necessary. Keep all styling local to components and files.
|
||||
|
||||
## Bun
|
||||
|
||||
Use bun and bunx not npm and npx
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -67,6 +67,9 @@ start-collector.sh
|
||||
# VSCode
|
||||
.vscode
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
|
||||
## Helm Chart Tests
|
||||
helm/sim/test
|
||||
i18n.cache
|
||||
|
||||
47
CLAUDE.md
Normal file
47
CLAUDE.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Expert Programming Standards
|
||||
|
||||
**You are tasked with implementing solutions that follow best practices. You MUST be accurate, elegant, and efficient as an expert programmer.**
|
||||
|
||||
---
|
||||
|
||||
# Role
|
||||
|
||||
You are a professional software engineer. All code you write MUST follow best practices, ensuring accuracy, quality, readability, and cleanliness. You MUST make FOCUSED EDITS that are EFFICIENT and ELEGANT.
|
||||
|
||||
## Logs
|
||||
|
||||
ENSURE that you use the logger.info and logger.warn and logger.error instead of the console.log whenever you want to display logs.
|
||||
|
||||
## Comments
|
||||
|
||||
You must use TSDOC for comments. Do not use ==== for comments to separate sections. Do not leave any comments that are not TSDOC.
|
||||
|
||||
## Global Styles
|
||||
|
||||
You should not update the global styles unless it is absolutely necessary. Keep all styling local to components and files.
|
||||
|
||||
## Bun
|
||||
|
||||
Use bun and bunx not npm and npx.
|
||||
|
||||
## Code Quality
|
||||
|
||||
- Write clean, maintainable code that follows the project's existing patterns
|
||||
- Prefer composition over inheritance
|
||||
- Keep functions small and focused on a single responsibility
|
||||
- Use meaningful variable and function names
|
||||
- Handle errors gracefully and provide useful error messages
|
||||
- Write type-safe code with proper TypeScript types
|
||||
|
||||
## Testing
|
||||
|
||||
- Write tests for new functionality when appropriate
|
||||
- Ensure existing tests pass before completing work
|
||||
- Follow the project's testing conventions
|
||||
|
||||
## Performance
|
||||
|
||||
- Consider performance implications of your code
|
||||
- Avoid unnecessary re-renders in React components
|
||||
- Use appropriate data structures and algorithms
|
||||
- Profile and optimize when necessary
|
||||
@@ -6,9 +6,10 @@ import Link from 'next/link'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { PageNavigationArrows } from '@/components/docs-layout/page-navigation-arrows'
|
||||
import { TOCFooter } from '@/components/docs-layout/toc-footer'
|
||||
import { LLMCopyButton } from '@/components/page-actions'
|
||||
import { StructuredData } from '@/components/structured-data'
|
||||
import { CodeBlock } from '@/components/ui/code-block'
|
||||
import { CopyPageButton } from '@/components/ui/copy-page-button'
|
||||
import { Heading } from '@/components/ui/heading'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
export default async function Page(props: { params: Promise<{ slug?: string[]; lang: string }> }) {
|
||||
@@ -202,7 +203,7 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
|
||||
<div className='relative mt-6 sm:mt-0'>
|
||||
<div className='absolute top-1 right-0 flex items-center gap-2'>
|
||||
<div className='hidden sm:flex'>
|
||||
<CopyPageButton markdownUrl={`${page.url}.mdx`} />
|
||||
<LLMCopyButton markdownUrl={`${page.url}.mdx`} />
|
||||
</div>
|
||||
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
|
||||
</div>
|
||||
@@ -214,6 +215,12 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
|
||||
components={{
|
||||
...defaultMdxComponents,
|
||||
CodeBlock,
|
||||
h1: (props) => <Heading as='h1' {...props} />,
|
||||
h2: (props) => <Heading as='h2' {...props} />,
|
||||
h3: (props) => <Heading as='h3' {...props} />,
|
||||
h4: (props) => <Heading as='h4' {...props} />,
|
||||
h5: (props) => <Heading as='h5' {...props} />,
|
||||
h6: (props) => <Heading as='h6' {...props} />,
|
||||
}}
|
||||
/>
|
||||
</DocsBody>
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
@import "fumadocs-ui/css/neutral.css";
|
||||
@import "fumadocs-ui/css/preset.css";
|
||||
|
||||
/* Prevent overscroll bounce effect on the page */
|
||||
html,
|
||||
body {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-fd-primary: #802fff; /* Purple from control-bar component */
|
||||
--font-geist-sans: var(--font-geist-sans);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { i18n } from '@/lib/i18n'
|
||||
import { getLLMText } from '@/lib/llms'
|
||||
import { source } from '@/lib/source'
|
||||
|
||||
@@ -7,7 +8,16 @@ export const revalidate = false
|
||||
|
||||
export async function GET(_req: NextRequest, { params }: { params: Promise<{ slug?: string[] }> }) {
|
||||
const { slug } = await params
|
||||
const page = source.getPage(slug)
|
||||
|
||||
let lang: (typeof i18n.languages)[number] = i18n.defaultLanguage
|
||||
let pageSlug = slug
|
||||
|
||||
if (slug && slug.length > 0 && i18n.languages.includes(slug[0] as typeof lang)) {
|
||||
lang = slug[0] as typeof lang
|
||||
pageSlug = slug.slice(1)
|
||||
}
|
||||
|
||||
const page = source.getPage(pageSlug, lang)
|
||||
if (!page) notFound()
|
||||
|
||||
return new NextResponse(await getLLMText(page), {
|
||||
|
||||
@@ -4170,3 +4170,32 @@ export function DuckDuckGoIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function RssIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
width='24'
|
||||
height='24'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
d='M4 11C6.38695 11 8.67613 11.9482 10.364 13.636C12.0518 15.3239 13 17.6131 13 20'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<path
|
||||
d='M4 4C8.24346 4 12.3131 5.68571 15.3137 8.68629C18.3143 11.6869 20 15.7565 20 20'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
/>
|
||||
<circle cx='5' cy='19' r='1' fill='currentColor' />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,55 +1,50 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useCopyButton } from 'fumadocs-ui/utils/use-copy-button'
|
||||
import { Check, Copy } from 'lucide-react'
|
||||
|
||||
const cache = new Map<string, string>()
|
||||
|
||||
interface CopyPageButtonProps {
|
||||
export function LLMCopyButton({
|
||||
markdownUrl,
|
||||
}: {
|
||||
/**
|
||||
* A URL to fetch the raw Markdown/MDX content of page
|
||||
*/
|
||||
markdownUrl: string
|
||||
}
|
||||
|
||||
export function CopyPageButton({ markdownUrl }: CopyPageButtonProps) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
}) {
|
||||
const [isLoading, setLoading] = useState(false)
|
||||
|
||||
const handleCopy = async () => {
|
||||
const [checked, onClick] = useCopyButton(async () => {
|
||||
const cached = cache.get(markdownUrl)
|
||||
if (cached) {
|
||||
await navigator.clipboard.writeText(cached)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
return
|
||||
}
|
||||
if (cached) return navigator.clipboard.writeText(cached)
|
||||
|
||||
setLoading(true)
|
||||
|
||||
try {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
'text/plain': fetch(markdownUrl).then(async (res) => {
|
||||
const content = await res.text()
|
||||
cache.set(markdownUrl, content)
|
||||
|
||||
return content
|
||||
}),
|
||||
}),
|
||||
])
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<button
|
||||
disabled={isLoading}
|
||||
onClick={handleCopy}
|
||||
onClick={onClick}
|
||||
className='flex cursor-pointer items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
|
||||
aria-label={copied ? 'Copied to clipboard' : 'Copy page content'}
|
||||
aria-label={checked ? 'Copied to clipboard' : 'Copy page content'}
|
||||
>
|
||||
{copied ? (
|
||||
{checked ? (
|
||||
<>
|
||||
<Check className='h-3.5 w-3.5' />
|
||||
<span>Copied</span>
|
||||
58
apps/docs/components/ui/heading.tsx
Normal file
58
apps/docs/components/ui/heading.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
'use client'
|
||||
|
||||
import { type ComponentPropsWithoutRef, useState } from 'react'
|
||||
import { Check, Link } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
||||
|
||||
interface HeadingProps extends ComponentPropsWithoutRef<'h1'> {
|
||||
as?: HeadingTag
|
||||
}
|
||||
|
||||
export function Heading({ as, className, ...props }: HeadingProps) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const As = as ?? 'h1'
|
||||
|
||||
if (!props.id) {
|
||||
return <As className={className} {...props} />
|
||||
}
|
||||
|
||||
const handleClick = async (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
const url = `${window.location.origin}${window.location.pathname}#${props.id}`
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(url)
|
||||
setCopied(true)
|
||||
|
||||
// Update URL hash without scrolling
|
||||
window.history.pushState(null, '', `#${props.id}`)
|
||||
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
} catch {
|
||||
// Fallback: just navigate to the anchor
|
||||
window.location.hash = props.id as string
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<As className={cn('group flex scroll-m-28 flex-row items-center gap-2', className)} {...props}>
|
||||
<a data-card='' href={`#${props.id}`} className='peer' onClick={handleClick}>
|
||||
{props.children}
|
||||
</a>
|
||||
{copied ? (
|
||||
<Check
|
||||
aria-hidden
|
||||
className='size-3.5 shrink-0 text-green-500 opacity-100 transition-opacity'
|
||||
/>
|
||||
) : (
|
||||
<Link
|
||||
aria-hidden
|
||||
className='size-3.5 shrink-0 text-fd-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 peer-hover:opacity-100'
|
||||
/>
|
||||
)}
|
||||
</As>
|
||||
)
|
||||
}
|
||||
@@ -27,14 +27,16 @@ Alle API-Antworten enthalten Informationen über Ihre Workflow-Ausführungslimit
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60, // Max sync workflow executions per minute
|
||||
"remaining": 58, // Remaining sync workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 60, // Sustained rate limit per minute
|
||||
"maxBurst": 120, // Maximum burst capacity
|
||||
"remaining": 118, // Current tokens available (up to maxBurst)
|
||||
"resetAt": "..." // When tokens next refill
|
||||
},
|
||||
"async": {
|
||||
"limit": 60, // Max async workflow executions per minute
|
||||
"remaining": 59, // Remaining async workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 200, // Sustained rate limit per minute
|
||||
"maxBurst": 400, // Maximum burst capacity
|
||||
"remaining": 398, // Current tokens available
|
||||
"resetAt": "..." // When tokens next refill
|
||||
}
|
||||
},
|
||||
"usage": {
|
||||
@@ -46,7 +48,7 @@ Alle API-Antworten enthalten Informationen über Ihre Workflow-Ausführungslimit
|
||||
}
|
||||
```
|
||||
|
||||
**Hinweis:** Die Ratenbegrenzungen in der Antwort beziehen sich auf Workflow-Ausführungen. Die Ratenbegrenzungen für den Aufruf dieses API-Endpunkts befinden sich in den Antwort-Headern (`X-RateLimit-*`).
|
||||
**Hinweis:** Ratenbegrenzungen verwenden einen Token-Bucket-Algorithmus. `remaining` kann `requestsPerMinute` bis zu `maxBurst` überschreiten, wenn du dein volles Kontingent in letzter Zeit nicht genutzt hast, was Burst-Traffic ermöglicht. Die Ratenbegrenzungen im Antworttext gelten für Workflow-Ausführungen. Die Ratenbegrenzungen für den Aufruf dieses API-Endpunkts befinden sich in den Antwort-Headern (`X-RateLimit-*`).
|
||||
|
||||
### Logs abfragen
|
||||
|
||||
@@ -110,13 +112,15 @@ Fragen Sie Workflow-Ausführungsprotokolle mit umfangreichen Filteroptionen ab.
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -190,13 +194,15 @@ Rufen Sie detaillierte Informationen zu einem bestimmten Logeintrag ab.
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -482,19 +488,27 @@ Fehlgeschlagene Webhook-Zustellungen werden mit exponentiellem Backoff und Jitte
|
||||
|
||||
## Rate-Limiting
|
||||
|
||||
Die API implementiert Rate-Limiting, um eine faire Nutzung zu gewährleisten:
|
||||
Die API verwendet einen **Token-Bucket-Algorithmus** für die Ratenbegrenzung, der eine faire Nutzung ermöglicht und gleichzeitig Burst-Traffic zulässt:
|
||||
|
||||
- **Kostenloser Plan**: 10 Anfragen pro Minute
|
||||
- **Pro-Plan**: 30 Anfragen pro Minute
|
||||
- **Team-Plan**: 60 Anfragen pro Minute
|
||||
- **Enterprise-Plan**: Individuelle Limits
|
||||
| Plan | Anfragen/Minute | Burst-Kapazität |
|
||||
|------|-----------------|----------------|
|
||||
| Free | 10 | 20 |
|
||||
| Pro | 30 | 60 |
|
||||
| Team | 60 | 120 |
|
||||
| Enterprise | 120 | 240 |
|
||||
|
||||
Rate-Limit-Informationen sind in den Antwort-Headern enthalten:
|
||||
- `X-RateLimit-Limit`: Maximale Anfragen pro Zeitfenster
|
||||
- `X-RateLimit-Remaining`: Verbleibende Anfragen im aktuellen Zeitfenster
|
||||
- `X-RateLimit-Reset`: ISO-Zeitstempel, wann das Zeitfenster zurückgesetzt wird
|
||||
**Wie es funktioniert:**
|
||||
- Tokens werden mit der Rate `requestsPerMinute` aufgefüllt
|
||||
- Du kannst im Leerlauf bis zu `maxBurst` Tokens ansammeln
|
||||
- Jede Anfrage verbraucht 1 Token
|
||||
- Die Burst-Kapazität ermöglicht die Bewältigung von Verkehrsspitzen
|
||||
|
||||
## Beispiel: Abfragen neuer Logs
|
||||
Informationen zur Ratenbegrenzung sind in den Antwort-Headern enthalten:
|
||||
- `X-RateLimit-Limit`: Anfragen pro Minute (Auffüllrate)
|
||||
- `X-RateLimit-Remaining`: Aktuell verfügbare Tokens
|
||||
- `X-RateLimit-Reset`: ISO-Zeitstempel, wann Tokens als nächstes aufgefüllt werden
|
||||
|
||||
## Beispiel: Abfragen nach neuen Logs
|
||||
|
||||
```javascript
|
||||
let cursor = null;
|
||||
@@ -541,7 +555,7 @@ async function pollLogs() {
|
||||
setInterval(pollLogs, 30000);
|
||||
```
|
||||
|
||||
## Beispiel: Verarbeiten von Webhooks
|
||||
## Beispiel: Verarbeitung von Webhooks
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
|
||||
@@ -147,8 +147,20 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
{
|
||||
"success": true,
|
||||
"rateLimit": {
|
||||
"sync": { "isLimited": false, "limit": 10, "remaining": 10, "resetAt": "2025-09-08T22:51:55.999Z" },
|
||||
"async": { "isLimited": false, "limit": 50, "remaining": 50, "resetAt": "2025-09-08T22:51:56.155Z" },
|
||||
"sync": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 25,
|
||||
"maxBurst": 50,
|
||||
"remaining": 50,
|
||||
"resetAt": "2025-09-08T22:51:55.999Z"
|
||||
},
|
||||
"async": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 400,
|
||||
"resetAt": "2025-09-08T22:51:56.155Z"
|
||||
},
|
||||
"authType": "api"
|
||||
},
|
||||
"usage": {
|
||||
@@ -159,49 +171,54 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**Rate-Limit-Felder:**
|
||||
- `requestsPerMinute`: Dauerhafte Rate-Begrenzung (Tokens werden mit dieser Rate aufgefüllt)
|
||||
- `maxBurst`: Maximale Tokens, die Sie ansammeln können (Burst-Kapazität)
|
||||
- `remaining`: Aktuell verfügbare Tokens (können bis zu `maxBurst` sein)
|
||||
|
||||
**Antwortfelder:**
|
||||
- `currentPeriodCost` zeigt die Nutzung im aktuellen Abrechnungszeitraum
|
||||
- `limit` wird aus individuellen Limits (Free/Pro) oder gebündelten Organisationslimits (Team/Enterprise) abgeleitet
|
||||
- `currentPeriodCost` spiegelt die Nutzung in der aktuellen Abrechnungsperiode wider
|
||||
- `limit` wird von individuellen Limits (Free/Pro) oder gepoolten Organisationslimits (Team/Enterprise) abgeleitet
|
||||
- `plan` ist der aktive Plan mit der höchsten Priorität, der mit Ihrem Benutzer verknüpft ist
|
||||
|
||||
## Plan-Limits
|
||||
|
||||
Verschiedene Abonnementpläne haben unterschiedliche Nutzungslimits:
|
||||
|
||||
| Plan | Monatliches Nutzungslimit | Ratengrenze (pro Minute) |
|
||||
| Plan | Monatliches Nutzungslimit | Rate-Limits (pro Minute) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **Free** | $10 | 5 sync, 10 async |
|
||||
| **Pro** | $100 | 10 sync, 50 async |
|
||||
| **Team** | $500 (gebündelt) | 50 sync, 100 async |
|
||||
| **Team** | $500 (gepoolt) | 50 sync, 100 async |
|
||||
| **Enterprise** | Individuell | Individuell |
|
||||
|
||||
## Abrechnungsmodell
|
||||
|
||||
Sim verwendet ein **Basisabonnement + Überschreitung** Abrechnungsmodell:
|
||||
Sim verwendet ein **Basisabonnement + Mehrverbrauch**-Abrechnungsmodell:
|
||||
|
||||
### Wie es funktioniert
|
||||
|
||||
**Pro Plan ($20/Monat):**
|
||||
**Pro-Plan ($20/Monat):**
|
||||
- Monatliches Abonnement beinhaltet $20 Nutzung
|
||||
- Nutzung unter $20 → Keine zusätzlichen Kosten
|
||||
- Nutzung über $20 → Zahlung der Überschreitung am Monatsende
|
||||
- Beispiel: $35 Nutzung = $20 (Abonnement) + $15 (Überschreitung)
|
||||
- Nutzung über $20 → Zahlen Sie den Mehrverbrauch am Monatsende
|
||||
- Beispiel: $35 Nutzung = $20 (Abonnement) + $15 (Mehrverbrauch)
|
||||
|
||||
**Team Plan ($40/Benutzer/Monat):**
|
||||
- Gebündelte Nutzung für alle Teammitglieder
|
||||
- Überschreitung wird aus der Gesamtnutzung des Teams berechnet
|
||||
**Team-Plan ($40/Benutzer/Monat):**
|
||||
- Gepoolte Nutzung für alle Teammitglieder
|
||||
- Mehrverbrauch wird aus der Gesamtnutzung des Teams berechnet
|
||||
- Organisationsinhaber erhält eine Rechnung
|
||||
|
||||
**Enterprise Pläne:**
|
||||
- Fester monatlicher Preis, keine Überschreitungen
|
||||
**Enterprise-Pläne:**
|
||||
- Fester monatlicher Preis, kein Mehrverbrauch
|
||||
- Individuelle Nutzungslimits gemäß Vereinbarung
|
||||
|
||||
### Schwellenwertabrechnung
|
||||
### Schwellenwert-Abrechnung
|
||||
|
||||
Wenn die nicht abgerechnete Überschreitung $50 erreicht, berechnet Sim automatisch den gesamten nicht abgerechneten Betrag.
|
||||
Wenn der nicht abgerechnete Mehrverbrauch $50 erreicht, berechnet Sim automatisch den gesamten nicht abgerechneten Betrag.
|
||||
|
||||
**Beispiel:**
|
||||
- Tag 10: $70 Überschreitung → Sofortige Abrechnung von $70
|
||||
- Tag 10: $70 Mehrverbrauch → Sofortige Abrechnung von $70
|
||||
- Tag 15: Zusätzliche $35 Nutzung ($105 insgesamt) → Bereits abgerechnet, keine Aktion
|
||||
- Tag 20: Weitere $50 Nutzung ($155 insgesamt, $85 nicht abgerechnet) → Sofortige Abrechnung von $85
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ Rufe eine Liste von Prognosemärkten von Kalshi mit optionaler Filterung ab
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Marktdaten und Metadaten |
|
||||
| `markets` | array | Array von Markt-Objekten |
|
||||
| `paging` | object | Paginierungscursor zum Abrufen weiterer Ergebnisse |
|
||||
|
||||
### `kalshi_get_market`
|
||||
|
||||
@@ -66,8 +66,7 @@ Rufe Details eines bestimmten Prognosemarkts nach Ticker ab
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Marktdaten und Metadaten |
|
||||
| `market` | object | Markt-Objekt mit Details |
|
||||
|
||||
### `kalshi_get_events`
|
||||
|
||||
@@ -85,10 +84,10 @@ Rufe eine Liste von Events von Kalshi mit optionaler Filterung ab
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Ereignisdaten und Metadaten |
|
||||
| `events` | array | Array von Ereignis-Objekten |
|
||||
| `paging` | object | Paginierungscursor zum Abrufen weiterer Ergebnisse |
|
||||
|
||||
### `kalshi_get_event`
|
||||
|
||||
@@ -103,10 +102,9 @@ Details eines bestimmten Ereignisses anhand des Tickers abrufen
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Ereignisdaten und Metadaten |
|
||||
| `event` | object | Ereignis-Objekt mit Details |
|
||||
|
||||
### `kalshi_get_balance`
|
||||
|
||||
@@ -121,10 +119,12 @@ Kontostand und Portfoliowert von Kalshi abrufen
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Kontostandsdaten und Metadaten |
|
||||
| `balance` | number | Kontostand in Cent |
|
||||
| `portfolioValue` | number | Portfoliowert in Cent |
|
||||
| `balanceDollars` | number | Kontostand in Dollar |
|
||||
| `portfolioValueDollars` | number | Portfoliowert in Dollar |
|
||||
|
||||
### `kalshi_get_positions`
|
||||
|
||||
@@ -146,8 +146,8 @@ Offene Positionen von Kalshi abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Positionsdaten und Metadaten |
|
||||
| `positions` | array | Array von Positions-Objekten |
|
||||
| `paging` | object | Paginierungscursor zum Abrufen weiterer Ergebnisse |
|
||||
|
||||
### `kalshi_get_orders`
|
||||
|
||||
@@ -169,8 +169,8 @@ Rufen Sie Ihre Bestellungen von Kalshi mit optionaler Filterung ab
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Bestelldaten und Metadaten |
|
||||
| `orders` | array | Array von Auftrags-Objekten |
|
||||
| `paging` | object | Paginierungscursor zum Abrufen weiterer Ergebnisse |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
@@ -188,8 +188,7 @@ Rufen Sie Details zu einem bestimmten Auftrag anhand der ID von Kalshi ab
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Auftragsdaten |
|
||||
| `order` | object | Auftrags-Objekt mit Details |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
@@ -205,8 +204,7 @@ Rufen Sie das Orderbuch (Ja- und Nein-Gebote) für einen bestimmten Markt ab
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Orderbuch-Daten und Metadaten |
|
||||
| `orderbook` | object | Orderbuch mit Ja/Nein-Geboten und -Anfragen |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
@@ -223,8 +221,8 @@ Rufen Sie aktuelle Trades über alle Märkte hinweg ab
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Handelsdaten und Metadaten |
|
||||
| `trades` | array | Array von Handelsobjekten |
|
||||
| `paging` | object | Paginierungscursor zum Abrufen weiterer Ergebnisse |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
@@ -244,8 +242,7 @@ OHLC-Kerzendaten für einen bestimmten Markt abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Kerzendaten und Metadaten |
|
||||
| `candlesticks` | array | Array von OHLC-Kerzendaten |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -268,8 +265,8 @@ Ihr Portfolio abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Ausführungsdaten und Metadaten |
|
||||
| `fills` | array | Array von Ausführungs-/Handelsobjekten |
|
||||
| `paging` | object | Paginierungscursor zum Abrufen weiterer Ergebnisse |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
@@ -285,8 +282,7 @@ Details einer bestimmten Marktserie nach Ticker abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Seriendaten und Metadaten |
|
||||
| `series` | object | Serienobjekt mit Details |
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
@@ -301,8 +297,7 @@ Den aktuellen Status der Kalshi-Börse abrufen (Handel und Börsenaktivität)
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Börsenstatus-Daten und Metadaten |
|
||||
| `status` | object | Börsenstatus mit trading_active und exchange_active Flags |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
@@ -336,8 +331,7 @@ Eine neue Order auf einem Kalshi-Prognosemarkt erstellen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Erstellte Auftragsdaten |
|
||||
| `order` | object | Das erstellte Auftragsobjekt |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
@@ -355,8 +349,8 @@ Einen bestehenden Auftrag auf Kalshi stornieren
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Stornierte Auftragsdaten |
|
||||
| `order` | object | Das stornierte Auftragsobjekt |
|
||||
| `reducedBy` | number | Anzahl der stornierten Kontrakte |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
@@ -384,8 +378,7 @@ Preis oder Menge eines bestehenden Auftrags auf Kalshi ändern
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Geänderte Auftragsdaten |
|
||||
| `order` | object | Das geänderte Auftragsobjekt |
|
||||
|
||||
## Hinweise
|
||||
|
||||
|
||||
@@ -44,14 +44,14 @@ Rufen Sie eine Liste von Prognosemärkten von Polymarket mit optionaler Filterun
|
||||
| `order` | string | Nein | Sortierfeld \(z.B. volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | Nein | Sortierrichtung \(true für aufsteigend, false für absteigend\) |
|
||||
| `tagId` | string | Nein | Nach Tag-ID filtern |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(max 50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Marktdaten und Metadaten |
|
||||
|
||||
### `polymarket_get_market`
|
||||
@@ -80,11 +80,11 @@ Ruft eine Liste von Events von Polymarket mit optionaler Filterung ab
|
||||
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | Nein | Nach geschlossenem Status filtern \(true/false\). Verwenden Sie false für nur aktive Events. |
|
||||
| `closed` | string | Nein | Nach geschlossenem Status filtern \(true/false\). Verwenden Sie false für nur aktive Ereignisse. |
|
||||
| `order` | string | Nein | Sortierfeld \(z.B. volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | Nein | Sortierrichtung \(true für aufsteigend, false für absteigend\) |
|
||||
| `tagId` | string | Nein | Nach Tag-ID filtern |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(max 50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
|
||||
#### Ausgabe
|
||||
@@ -107,10 +107,10 @@ Ruft Details eines bestimmten Events nach ID oder Slug ab
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Ereignisdaten und Metadaten |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Event-Daten und Metadaten |
|
||||
|
||||
### `polymarket_get_tags`
|
||||
|
||||
@@ -118,16 +118,16 @@ Verfügbare Tags zum Filtern von Märkten von Polymarket abrufen
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Erforderlich | Beschreibung |
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(max 50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Tag-Daten und Metadaten |
|
||||
|
||||
### `polymarket_search`
|
||||
@@ -136,17 +136,17 @@ Nach Märkten, Ereignissen und Profilen auf Polymarket suchen
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Erforderlich | Beschreibung |
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | Ja | Suchbegriff |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(max 50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Suchergebnisse und Metadaten |
|
||||
|
||||
### `polymarket_get_series`
|
||||
@@ -155,17 +155,16 @@ Serien (verwandte Marktgruppen) von Polymarket abrufen
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Erforderlich | Beschreibung |
|
||||
| Parameter | Typ | Erforderlich | Beschreibung |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(max 50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Seriendaten und Metadaten |
|
||||
| `series` | array | Array von Serien-Objekten |
|
||||
|
||||
### `polymarket_get_series_by_id`
|
||||
|
||||
@@ -179,10 +178,9 @@ Eine bestimmte Serie (zugehörige Marktgruppe) anhand der ID von Polymarket abru
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Seriendaten und Metadaten |
|
||||
| `series` | object | Serien-Objekt mit Details |
|
||||
|
||||
### `polymarket_get_orderbook`
|
||||
|
||||
@@ -196,10 +194,9 @@ Die Orderbuch-Zusammenfassung für einen bestimmten Token abrufen
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Beschreibung |
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Orderbuch-Daten und Metadaten |
|
||||
| `orderbook` | object | Orderbuch mit Geld- und Briefkurs-Arrays |
|
||||
|
||||
### `polymarket_get_price`
|
||||
|
||||
@@ -216,8 +213,7 @@ Den Marktpreis für einen bestimmten Token und eine bestimmte Seite abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Preisdaten und Metadaten |
|
||||
| `price` | string | Marktpreis |
|
||||
|
||||
### `polymarket_get_midpoint`
|
||||
|
||||
@@ -233,8 +229,7 @@ Abrufen des Mittelpreises für einen bestimmten Token
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Mittelpreisdaten und Metadaten |
|
||||
| `midpoint` | string | Mittelkurs |
|
||||
|
||||
### `polymarket_get_price_history`
|
||||
|
||||
@@ -254,8 +249,7 @@ Abrufen historischer Preisdaten für einen bestimmten Markttoken
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Preisverlaufsdaten und Metadaten |
|
||||
| `history` | array | Array von Preisverlaufseinträgen mit Zeitstempel \(t\) und Preis \(p\) |
|
||||
|
||||
### `polymarket_get_last_trade_price`
|
||||
|
||||
@@ -271,8 +265,7 @@ Den letzten Handelspreis für einen bestimmten Token abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Letzter Handelspreis und Metadaten |
|
||||
| `price` | string | Letzter Handelspreis |
|
||||
|
||||
### `polymarket_get_spread`
|
||||
|
||||
@@ -288,8 +281,7 @@ Die Geld-Brief-Spanne für einen bestimmten Token abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Spread-Daten und Metadaten |
|
||||
| `spread` | object | Geld-Brief-Spanne mit Geld- und Briefkursen |
|
||||
|
||||
### `polymarket_get_tick_size`
|
||||
|
||||
@@ -305,8 +297,7 @@ Die minimale Tickgröße für einen bestimmten Token abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Status des Operationserfolgs |
|
||||
| `output` | object | Tickgröße und Metadaten |
|
||||
| `tickSize` | string | Minimale Tick-Größe |
|
||||
|
||||
### `polymarket_get_positions`
|
||||
|
||||
@@ -323,8 +314,7 @@ Benutzerpositionen von Polymarket abrufen
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Positionsdaten und Metadaten |
|
||||
| `positions` | array | Array von Positions-Objekten |
|
||||
|
||||
### `polymarket_get_trades`
|
||||
|
||||
@@ -336,15 +326,14 @@ Handelshistorie von Polymarket abrufen
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `user` | string | Nein | Wallet-Adresse des Benutzers zum Filtern von Trades |
|
||||
| `market` | string | Nein | Markt-ID zum Filtern von Trades |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(empfohlen: 25-50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe so viele Ergebnisse\) |
|
||||
| `limit` | string | Nein | Anzahl der Ergebnisse pro Seite \(max 50\) |
|
||||
| `offset` | string | Nein | Paginierungsoffset \(überspringe diese Anzahl an Ergebnissen\) |
|
||||
|
||||
#### Ausgabe
|
||||
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `output` | object | Handelsdaten und Metadaten |
|
||||
| `trades` | array | Array von Handelsobjekten |
|
||||
|
||||
## Hinweise
|
||||
|
||||
|
||||
@@ -342,19 +342,30 @@ Eine E-Mail-Vorlage von SendGrid löschen
|
||||
| Parameter | Typ | Beschreibung |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Erfolgsstatus der Operation |
|
||||
| `messageId` | string | E-Mail-Nachrichten-ID (send_mail) |
|
||||
| `message` | string | Status- oder Erfolgsmeldung |
|
||||
| `messageId` | string | E-Mail-Nachrichten-ID \(send_mail\) |
|
||||
| `to` | string | E-Mail-Adresse des Empfängers \(send_mail\) |
|
||||
| `subject` | string | E-Mail-Betreff \(send_mail, create_template_version\) |
|
||||
| `id` | string | Ressourcen-ID |
|
||||
| `jobId` | string | Job-ID für asynchrone Operationen |
|
||||
| `email` | string | E-Mail-Adresse |
|
||||
| `firstName` | string | Vorname |
|
||||
| `lastName` | string | Nachname |
|
||||
| `email` | string | E-Mail-Adresse des Kontakts |
|
||||
| `firstName` | string | Vorname des Kontakts |
|
||||
| `lastName` | string | Nachname des Kontakts |
|
||||
| `createdAt` | string | Erstellungszeitstempel |
|
||||
| `updatedAt` | string | Zeitstempel der letzten Aktualisierung |
|
||||
| `listIds` | json | Array von Listen-IDs, zu denen der Kontakt gehört |
|
||||
| `customFields` | json | Benutzerdefinierte Feldwerte |
|
||||
| `contacts` | json | Array von Kontakten |
|
||||
| `contactCount` | number | Anzahl der Kontakte |
|
||||
| `lists` | json | Array von Listen |
|
||||
| `templates` | json | Array von Vorlagen |
|
||||
| `message` | string | Status- oder Erfolgsmeldung |
|
||||
| `name` | string | Ressourcenname |
|
||||
| `templates` | json | Array von Vorlagen |
|
||||
| `generation` | string | Vorlagengeneration |
|
||||
| `versions` | json | Array von Vorlagenversionen |
|
||||
| `templateId` | string | Vorlagen-ID |
|
||||
| `active` | boolean | Ob die Vorlagenversion aktiv ist |
|
||||
| `htmlContent` | string | HTML-Inhalt |
|
||||
| `plainContent` | string | Nur-Text-Inhalt |
|
||||
|
||||
### `sendgrid_create_template_version`
|
||||
|
||||
|
||||
@@ -30,15 +30,19 @@ Verwende den Start-Block für alles, was aus dem Editor, deploy-to-API oder depl
|
||||
<Card title="Schedule" href="/triggers/schedule">
|
||||
Cron- oder intervallbasierte Ausführung
|
||||
</Card>
|
||||
<Card title="RSS Feed" href="/triggers/rss">
|
||||
RSS- und Atom-Feeds auf neue Inhalte überwachen
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Schneller Vergleich
|
||||
|
||||
| Trigger | Startbedingung |
|
||||
|---------|-----------------|
|
||||
| **Start** | Editor-Ausführungen, deploy-to-API Anfragen oder Chat-Nachrichten |
|
||||
| **Start** | Editor-Ausführungen, Deploy-to-API-Anfragen oder Chat-Nachrichten |
|
||||
| **Schedule** | Timer, der im Schedule-Block verwaltet wird |
|
||||
| **Webhook** | Bei eingehender HTTP-Anfrage |
|
||||
| **RSS Feed** | Neues Element im Feed veröffentlicht |
|
||||
|
||||
> Der Start-Block stellt immer `input`, `conversationId` und `files` Felder bereit. Füge benutzerdefinierte Felder zum Eingabeformat für zusätzliche strukturierte Daten hinzu.
|
||||
|
||||
|
||||
49
apps/docs/content/docs/de/triggers/rss.mdx
Normal file
49
apps/docs/content/docs/de/triggers/rss.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: RSS-Feed
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
Der RSS-Feed-Block überwacht RSS- und Atom-Feeds – wenn neue Einträge veröffentlicht werden, wird Ihr Workflow automatisch ausgelöst.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/rss.png"
|
||||
alt="RSS-Feed-Block"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Konfiguration
|
||||
|
||||
1. **RSS-Feed-Block hinzufügen** - Ziehen Sie den RSS-Feed-Block, um Ihren Workflow zu starten
|
||||
2. **Feed-URL eingeben** - Fügen Sie die URL eines beliebigen RSS- oder Atom-Feeds ein
|
||||
3. **Bereitstellen** - Stellen Sie Ihren Workflow bereit, um das Polling zu aktivieren
|
||||
|
||||
Nach der Bereitstellung wird der Feed jede Minute auf neue Einträge überprüft.
|
||||
|
||||
## Ausgabefelder
|
||||
|
||||
| Feld | Typ | Beschreibung |
|
||||
|-------|------|-------------|
|
||||
| `title` | string | Titel des Eintrags |
|
||||
| `link` | string | Link des Eintrags |
|
||||
| `pubDate` | string | Veröffentlichungsdatum |
|
||||
| `item` | object | Rohdaten des Eintrags mit allen Feldern |
|
||||
| `feed` | object | Rohdaten der Feed-Metadaten |
|
||||
|
||||
Greifen Sie direkt auf zugeordnete Felder zu (`<rss.title>`) oder verwenden Sie die Rohobjekte für beliebige Felder (`<rss.item.author>`, `<rss.feed.language>`).
|
||||
|
||||
## Anwendungsfälle
|
||||
|
||||
- **Inhaltsüberwachung** - Verfolgen Sie Blogs, Nachrichtenseiten oder Updates von Wettbewerbern
|
||||
- **Podcast-Automatisierung** - Lösen Sie Workflows aus, wenn neue Episoden erscheinen
|
||||
- **Release-Tracking** - Überwachen Sie GitHub-Releases, Changelogs oder Produkt-Updates
|
||||
- **Social-Media-Aggregation** - Sammeln Sie Inhalte von Plattformen, die RSS-Feeds anbieten
|
||||
|
||||
<Callout>
|
||||
RSS-Trigger werden nur für Einträge ausgelöst, die nach dem Speichern des Triggers veröffentlicht wurden. Bestehende Feed-Einträge werden nicht verarbeitet.
|
||||
</Callout>
|
||||
@@ -27,14 +27,16 @@ All API responses include information about your workflow execution limits and u
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60, // Max sync workflow executions per minute
|
||||
"remaining": 58, // Remaining sync workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 60, // Sustained rate limit per minute
|
||||
"maxBurst": 120, // Maximum burst capacity
|
||||
"remaining": 118, // Current tokens available (up to maxBurst)
|
||||
"resetAt": "..." // When tokens next refill
|
||||
},
|
||||
"async": {
|
||||
"limit": 60, // Max async workflow executions per minute
|
||||
"remaining": 59, // Remaining async workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 200, // Sustained rate limit per minute
|
||||
"maxBurst": 400, // Maximum burst capacity
|
||||
"remaining": 398, // Current tokens available
|
||||
"resetAt": "..." // When tokens next refill
|
||||
}
|
||||
},
|
||||
"usage": {
|
||||
@@ -46,7 +48,7 @@ All API responses include information about your workflow execution limits and u
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The rate limits in the response body are for workflow executions. The rate limits for calling this API endpoint are in the response headers (`X-RateLimit-*`).
|
||||
**Note:** Rate limits use a token bucket algorithm. `remaining` can exceed `requestsPerMinute` up to `maxBurst` when you haven't used your full allowance recently, allowing for burst traffic. The rate limits in the response body are for workflow executions. The rate limits for calling this API endpoint are in the response headers (`X-RateLimit-*`).
|
||||
|
||||
### Query Logs
|
||||
|
||||
@@ -108,13 +110,15 @@ Query workflow execution logs with extensive filtering options.
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -184,13 +188,15 @@ Retrieve detailed information about a specific log entry.
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -467,17 +473,25 @@ Failed webhook deliveries are retried with exponential backoff and jitter:
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
The API implements rate limiting to ensure fair usage:
|
||||
The API uses a **token bucket algorithm** for rate limiting, providing fair usage while allowing burst traffic:
|
||||
|
||||
- **Free plan**: 10 requests per minute
|
||||
- **Pro plan**: 30 requests per minute
|
||||
- **Team plan**: 60 requests per minute
|
||||
- **Enterprise plan**: Custom limits
|
||||
| Plan | Requests/Minute | Burst Capacity |
|
||||
|------|-----------------|----------------|
|
||||
| Free | 10 | 20 |
|
||||
| Pro | 30 | 60 |
|
||||
| Team | 60 | 120 |
|
||||
| Enterprise | 120 | 240 |
|
||||
|
||||
**How it works:**
|
||||
- Tokens refill at `requestsPerMinute` rate
|
||||
- You can accumulate up to `maxBurst` tokens when idle
|
||||
- Each request consumes 1 token
|
||||
- Burst capacity allows handling traffic spikes
|
||||
|
||||
Rate limit information is included in response headers:
|
||||
- `X-RateLimit-Limit`: Maximum requests per window
|
||||
- `X-RateLimit-Remaining`: Requests remaining in current window
|
||||
- `X-RateLimit-Reset`: ISO timestamp when the window resets
|
||||
- `X-RateLimit-Limit`: Requests per minute (refill rate)
|
||||
- `X-RateLimit-Remaining`: Current tokens available
|
||||
- `X-RateLimit-Reset`: ISO timestamp when tokens next refill
|
||||
|
||||
## Example: Polling for New Logs
|
||||
|
||||
|
||||
@@ -143,8 +143,20 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
{
|
||||
"success": true,
|
||||
"rateLimit": {
|
||||
"sync": { "isLimited": false, "limit": 10, "remaining": 10, "resetAt": "2025-09-08T22:51:55.999Z" },
|
||||
"async": { "isLimited": false, "limit": 50, "remaining": 50, "resetAt": "2025-09-08T22:51:56.155Z" },
|
||||
"sync": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 25,
|
||||
"maxBurst": 50,
|
||||
"remaining": 50,
|
||||
"resetAt": "2025-09-08T22:51:55.999Z"
|
||||
},
|
||||
"async": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 400,
|
||||
"resetAt": "2025-09-08T22:51:56.155Z"
|
||||
},
|
||||
"authType": "api"
|
||||
},
|
||||
"usage": {
|
||||
@@ -155,6 +167,11 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**Rate Limit Fields:**
|
||||
- `requestsPerMinute`: Sustained rate limit (tokens refill at this rate)
|
||||
- `maxBurst`: Maximum tokens you can accumulate (burst capacity)
|
||||
- `remaining`: Current tokens available (can be up to `maxBurst`)
|
||||
|
||||
**Response Fields:**
|
||||
- `currentPeriodCost` reflects usage in the current billing period
|
||||
- `limit` is derived from individual limits (Free/Pro) or pooled organization limits (Team/Enterprise)
|
||||
|
||||
@@ -52,8 +52,8 @@ Retrieve a list of prediction markets from Kalshi with optional filtering
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Markets data and metadata |
|
||||
| `markets` | array | Array of market objects |
|
||||
| `paging` | object | Pagination cursor for fetching more results |
|
||||
|
||||
### `kalshi_get_market`
|
||||
|
||||
@@ -69,8 +69,7 @@ Retrieve details of a specific prediction market by ticker
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Market data and metadata |
|
||||
| `market` | object | Market object with details |
|
||||
|
||||
### `kalshi_get_events`
|
||||
|
||||
@@ -90,8 +89,8 @@ Retrieve a list of events from Kalshi with optional filtering
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Events data and metadata |
|
||||
| `events` | array | Array of event objects |
|
||||
| `paging` | object | Pagination cursor for fetching more results |
|
||||
|
||||
### `kalshi_get_event`
|
||||
|
||||
@@ -108,8 +107,7 @@ Retrieve details of a specific event by ticker
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Event data and metadata |
|
||||
| `event` | object | Event object with details |
|
||||
|
||||
### `kalshi_get_balance`
|
||||
|
||||
@@ -126,8 +124,10 @@ Retrieve your account balance and portfolio value from Kalshi
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Balance data and metadata |
|
||||
| `balance` | number | Account balance in cents |
|
||||
| `portfolioValue` | number | Portfolio value in cents |
|
||||
| `balanceDollars` | number | Account balance in dollars |
|
||||
| `portfolioValueDollars` | number | Portfolio value in dollars |
|
||||
|
||||
### `kalshi_get_positions`
|
||||
|
||||
@@ -149,8 +149,8 @@ Retrieve your open positions from Kalshi
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Positions data and metadata |
|
||||
| `positions` | array | Array of position objects |
|
||||
| `paging` | object | Pagination cursor for fetching more results |
|
||||
|
||||
### `kalshi_get_orders`
|
||||
|
||||
@@ -172,8 +172,8 @@ Retrieve your orders from Kalshi with optional filtering
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Orders data and metadata |
|
||||
| `orders` | array | Array of order objects |
|
||||
| `paging` | object | Pagination cursor for fetching more results |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
@@ -191,8 +191,7 @@ Retrieve details of a specific order by ID from Kalshi
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Order data |
|
||||
| `order` | object | Order object with details |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
@@ -208,8 +207,7 @@ Retrieve the orderbook (yes and no bids) for a specific market
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Orderbook data and metadata |
|
||||
| `orderbook` | object | Orderbook with yes/no bids and asks |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
@@ -226,8 +224,8 @@ Retrieve recent trades across all markets
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Trades data and metadata |
|
||||
| `trades` | array | Array of trade objects |
|
||||
| `paging` | object | Pagination cursor for fetching more results |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
@@ -247,8 +245,7 @@ Retrieve OHLC candlestick data for a specific market
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Candlestick data and metadata |
|
||||
| `candlesticks` | array | Array of OHLC candlestick data |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -271,8 +268,8 @@ Retrieve your portfolio
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Fills data and metadata |
|
||||
| `fills` | array | Array of fill/trade objects |
|
||||
| `paging` | object | Pagination cursor for fetching more results |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
@@ -288,8 +285,7 @@ Retrieve details of a specific market series by ticker
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Series data and metadata |
|
||||
| `series` | object | Series object with details |
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
@@ -304,8 +300,7 @@ Retrieve the current status of the Kalshi exchange (trading and exchange activit
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Exchange status data and metadata |
|
||||
| `status` | object | Exchange status with trading_active and exchange_active flags |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
@@ -339,8 +334,7 @@ Create a new order on a Kalshi prediction market
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Created order data |
|
||||
| `order` | object | The created order object |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
@@ -358,8 +352,8 @@ Cancel an existing order on Kalshi
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Canceled order data |
|
||||
| `order` | object | The canceled order object |
|
||||
| `reducedBy` | number | Number of contracts canceled |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
@@ -387,8 +381,7 @@ Modify the price or quantity of an existing order on Kalshi
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Amended order data |
|
||||
| `order` | object | The amended order object |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,15 +47,14 @@ Retrieve a list of prediction markets from Polymarket with optional filtering
|
||||
| `order` | string | No | Sort field \(e.g., volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
|
||||
| `tagId` | string | No | Filter by tag ID |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
| `limit` | string | No | Number of results per page \(max 50\) |
|
||||
| `offset` | string | No | Pagination offset \(skip this many results\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Markets data and metadata |
|
||||
| `markets` | array | Array of market objects |
|
||||
|
||||
### `polymarket_get_market`
|
||||
|
||||
@@ -72,8 +71,7 @@ Retrieve details of a specific prediction market by ID or slug
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Market data and metadata |
|
||||
| `market` | object | Market object with details |
|
||||
|
||||
### `polymarket_get_events`
|
||||
|
||||
@@ -87,15 +85,14 @@ Retrieve a list of events from Polymarket with optional filtering
|
||||
| `order` | string | No | Sort field \(e.g., volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | No | Sort direction \(true for ascending, false for descending\) |
|
||||
| `tagId` | string | No | Filter by tag ID |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
| `limit` | string | No | Number of results per page \(max 50\) |
|
||||
| `offset` | string | No | Pagination offset \(skip this many results\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Events data and metadata |
|
||||
| `events` | array | Array of event objects |
|
||||
|
||||
### `polymarket_get_event`
|
||||
|
||||
@@ -112,8 +109,7 @@ Retrieve details of a specific event by ID or slug
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Event data and metadata |
|
||||
| `event` | object | Event object with details |
|
||||
|
||||
### `polymarket_get_tags`
|
||||
|
||||
@@ -123,15 +119,14 @@ Retrieve available tags for filtering markets from Polymarket
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
| `limit` | string | No | Number of results per page \(max 50\) |
|
||||
| `offset` | string | No | Pagination offset \(skip this many results\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Tags data and metadata |
|
||||
| `tags` | array | Array of tag objects with id, label, and slug |
|
||||
|
||||
### `polymarket_search`
|
||||
|
||||
@@ -142,15 +137,14 @@ Search for markets, events, and profiles on Polymarket
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | Yes | Search query term |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
| `offset` | string | No | Pagination offset \(skip this many results\) |
|
||||
| `limit` | string | No | Number of results per page \(max 50\) |
|
||||
| `offset` | string | No | Pagination offset |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Search results and metadata |
|
||||
| `results` | object | Search results containing markets, events, and profiles arrays |
|
||||
|
||||
### `polymarket_get_series`
|
||||
|
||||
@@ -160,15 +154,14 @@ Retrieve series (related market groups) from Polymarket
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
| `limit` | string | No | Number of results per page \(max 50\) |
|
||||
| `offset` | string | No | Pagination offset \(skip this many results\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Series data and metadata |
|
||||
| `series` | array | Array of series objects |
|
||||
|
||||
### `polymarket_get_series_by_id`
|
||||
|
||||
@@ -184,8 +177,7 @@ Retrieve a specific series (related market group) by ID from Polymarket
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Series data and metadata |
|
||||
| `series` | object | Series object with details |
|
||||
|
||||
### `polymarket_get_orderbook`
|
||||
|
||||
@@ -201,8 +193,7 @@ Retrieve the order book summary for a specific token
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Orderbook data and metadata |
|
||||
| `orderbook` | object | Order book with bids and asks arrays |
|
||||
|
||||
### `polymarket_get_price`
|
||||
|
||||
@@ -219,8 +210,7 @@ Retrieve the market price for a specific token and side
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Price data and metadata |
|
||||
| `price` | string | Market price |
|
||||
|
||||
### `polymarket_get_midpoint`
|
||||
|
||||
@@ -236,8 +226,7 @@ Retrieve the midpoint price for a specific token
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Midpoint price data and metadata |
|
||||
| `midpoint` | string | Midpoint price |
|
||||
|
||||
### `polymarket_get_price_history`
|
||||
|
||||
@@ -257,8 +246,7 @@ Retrieve historical price data for a specific market token
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Price history data and metadata |
|
||||
| `history` | array | Array of price history entries with timestamp \(t\) and price \(p\) |
|
||||
|
||||
### `polymarket_get_last_trade_price`
|
||||
|
||||
@@ -274,8 +262,7 @@ Retrieve the last trade price for a specific token
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Last trade price and metadata |
|
||||
| `price` | string | Last trade price |
|
||||
|
||||
### `polymarket_get_spread`
|
||||
|
||||
@@ -291,8 +278,7 @@ Retrieve the bid-ask spread for a specific token
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Spread data and metadata |
|
||||
| `spread` | object | Bid-ask spread with bid and ask prices |
|
||||
|
||||
### `polymarket_get_tick_size`
|
||||
|
||||
@@ -308,8 +294,7 @@ Retrieve the minimum tick size for a specific token
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Tick size and metadata |
|
||||
| `tickSize` | string | Minimum tick size |
|
||||
|
||||
### `polymarket_get_positions`
|
||||
|
||||
@@ -326,8 +311,7 @@ Retrieve user positions from Polymarket
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Positions data and metadata |
|
||||
| `positions` | array | Array of position objects |
|
||||
|
||||
### `polymarket_get_trades`
|
||||
|
||||
@@ -339,15 +323,14 @@ Retrieve trade history from Polymarket
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `user` | string | No | User wallet address to filter trades |
|
||||
| `market` | string | No | Market ID to filter trades |
|
||||
| `limit` | string | No | Number of results per page \(recommended: 25-50\) |
|
||||
| `limit` | string | No | Number of results per page \(max 50\) |
|
||||
| `offset` | string | No | Pagination offset \(skip this many results\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `output` | object | Trades data and metadata |
|
||||
| `trades` | array | Array of trade objects |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -345,19 +345,30 @@ Delete an email template from SendGrid
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Operation success status |
|
||||
| `message` | string | Status or success message |
|
||||
| `messageId` | string | Email message ID \(send_mail\) |
|
||||
| `to` | string | Recipient email address \(send_mail\) |
|
||||
| `subject` | string | Email subject \(send_mail, create_template_version\) |
|
||||
| `id` | string | Resource ID |
|
||||
| `jobId` | string | Job ID for async operations |
|
||||
| `email` | string | Email address |
|
||||
| `firstName` | string | First name |
|
||||
| `lastName` | string | Last name |
|
||||
| `email` | string | Contact email address |
|
||||
| `firstName` | string | Contact first name |
|
||||
| `lastName` | string | Contact last name |
|
||||
| `createdAt` | string | Creation timestamp |
|
||||
| `updatedAt` | string | Last update timestamp |
|
||||
| `listIds` | json | Array of list IDs the contact belongs to |
|
||||
| `customFields` | json | Custom field values |
|
||||
| `contacts` | json | Array of contacts |
|
||||
| `contactCount` | number | Number of contacts |
|
||||
| `lists` | json | Array of lists |
|
||||
| `templates` | json | Array of templates |
|
||||
| `message` | string | Status or success message |
|
||||
| `name` | string | Resource name |
|
||||
| `templates` | json | Array of templates |
|
||||
| `generation` | string | Template generation |
|
||||
| `versions` | json | Array of template versions |
|
||||
| `templateId` | string | Template ID |
|
||||
| `active` | boolean | Whether template version is active |
|
||||
| `htmlContent` | string | HTML content |
|
||||
| `plainContent` | string | Plain text content |
|
||||
|
||||
### `sendgrid_create_template_version`
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ Use the Start block for everything originating from the editor, deploy-to-API, o
|
||||
<Card title="Schedule" href="/triggers/schedule">
|
||||
Cron or interval based execution
|
||||
</Card>
|
||||
<Card title="RSS Feed" href="/triggers/rss">
|
||||
Monitor RSS and Atom feeds for new content
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Quick Comparison
|
||||
@@ -39,6 +42,7 @@ Use the Start block for everything originating from the editor, deploy-to-API, o
|
||||
| **Start** | Editor runs, deploy-to-API requests, or chat messages |
|
||||
| **Schedule** | Timer managed in schedule block |
|
||||
| **Webhook** | On inbound HTTP request |
|
||||
| **RSS Feed** | New item published to feed |
|
||||
|
||||
> The Start block always exposes `input`, `conversationId`, and `files` fields. Add custom fields to the input format for additional structured data.
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"pages": ["index", "start", "schedule", "webhook"]
|
||||
"pages": ["index", "start", "schedule", "webhook", "rss"]
|
||||
}
|
||||
|
||||
49
apps/docs/content/docs/en/triggers/rss.mdx
Normal file
49
apps/docs/content/docs/en/triggers/rss.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: RSS Feed
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
The RSS Feed block monitors RSS and Atom feeds – when new items are published, your workflow triggers automatically.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/rss.png"
|
||||
alt="RSS Feed Block"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Configuration
|
||||
|
||||
1. **Add RSS Feed Block** - Drag the RSS Feed block to start your workflow
|
||||
2. **Enter Feed URL** - Paste the URL of any RSS or Atom feed
|
||||
3. **Deploy** - Deploy your workflow to activate polling
|
||||
|
||||
Once deployed, the feed is checked every minute for new items.
|
||||
|
||||
## Output Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `title` | string | Item title |
|
||||
| `link` | string | Item link |
|
||||
| `pubDate` | string | Publication date |
|
||||
| `item` | object | Raw item with all fields |
|
||||
| `feed` | object | Raw feed metadata |
|
||||
|
||||
Access mapped fields directly (`<rss.title>`) or use the raw objects for any field (`<rss.item.author>`, `<rss.feed.language>`).
|
||||
|
||||
## Use Cases
|
||||
|
||||
- **Content monitoring** - Track blogs, news sites, or competitor updates
|
||||
- **Podcast automation** - Trigger workflows when new episodes drop
|
||||
- **Release tracking** - Monitor GitHub releases, changelogs, or product updates
|
||||
- **Social aggregation** - Collect content from platforms that expose RSS feeds
|
||||
|
||||
<Callout>
|
||||
RSS triggers only fire for items published after you save the trigger. Existing feed items are not processed.
|
||||
</Callout>
|
||||
@@ -27,14 +27,16 @@ Todas las respuestas de la API incluyen información sobre tus límites de ejecu
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60, // Max sync workflow executions per minute
|
||||
"remaining": 58, // Remaining sync workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 60, // Sustained rate limit per minute
|
||||
"maxBurst": 120, // Maximum burst capacity
|
||||
"remaining": 118, // Current tokens available (up to maxBurst)
|
||||
"resetAt": "..." // When tokens next refill
|
||||
},
|
||||
"async": {
|
||||
"limit": 60, // Max async workflow executions per minute
|
||||
"remaining": 59, // Remaining async workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 200, // Sustained rate limit per minute
|
||||
"maxBurst": 400, // Maximum burst capacity
|
||||
"remaining": 398, // Current tokens available
|
||||
"resetAt": "..." // When tokens next refill
|
||||
}
|
||||
},
|
||||
"usage": {
|
||||
@@ -46,7 +48,7 @@ Todas las respuestas de la API incluyen información sobre tus límites de ejecu
|
||||
}
|
||||
```
|
||||
|
||||
**Nota:** Los límites de tasa en el cuerpo de la respuesta son para ejecuciones de flujos de trabajo. Los límites de tasa para llamar a este endpoint de la API están en los encabezados de respuesta (`X-RateLimit-*`).
|
||||
**Nota:** Los límites de tasa utilizan un algoritmo de cubo de tokens. `remaining` puede exceder `requestsPerMinute` hasta `maxBurst` cuando no has usado tu asignación completa recientemente, permitiendo tráfico en ráfagas. Los límites de tasa en el cuerpo de la respuesta son para ejecuciones de flujo de trabajo. Los límites de tasa para llamar a este punto final de la API están en los encabezados de respuesta (`X-RateLimit-*`).
|
||||
|
||||
### Consultar registros
|
||||
|
||||
@@ -110,13 +112,15 @@ Consulta los registros de ejecución de flujos de trabajo con amplias opciones d
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -190,13 +194,15 @@ Recupera información detallada sobre una entrada de registro específica.
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -482,19 +488,27 @@ Las entregas de webhook fallidas se reintentan con retroceso exponencial y fluct
|
||||
|
||||
## Limitación de tasa
|
||||
|
||||
La API implementa limitación de tasa para asegurar un uso justo:
|
||||
La API utiliza un **algoritmo de cubo de tokens** para limitar la tasa, proporcionando un uso justo mientras permite tráfico en ráfagas:
|
||||
|
||||
- **Plan gratuito**: 10 solicitudes por minuto
|
||||
- **Plan Pro**: 30 solicitudes por minuto
|
||||
- **Plan Team**: 60 solicitudes por minuto
|
||||
- **Plan Enterprise**: Límites personalizados
|
||||
| Plan | Solicitudes/Minuto | Capacidad de ráfaga |
|
||||
|------|-----------------|----------------|
|
||||
| Free | 10 | 20 |
|
||||
| Pro | 30 | 60 |
|
||||
| Team | 60 | 120 |
|
||||
| Enterprise | 120 | 240 |
|
||||
|
||||
La información de límite de tasa se incluye en las cabeceras de respuesta:
|
||||
- `X-RateLimit-Limit`: Solicitudes máximas por ventana
|
||||
- `X-RateLimit-Remaining`: Solicitudes restantes en la ventana actual
|
||||
- `X-RateLimit-Reset`: Marca de tiempo ISO cuando la ventana se reinicia
|
||||
**Cómo funciona:**
|
||||
- Los tokens se recargan a una tasa de `requestsPerMinute`
|
||||
- Puedes acumular hasta `maxBurst` tokens cuando estás inactivo
|
||||
- Cada solicitud consume 1 token
|
||||
- La capacidad de ráfaga permite manejar picos de tráfico
|
||||
|
||||
## Ejemplo: Sondeo de nuevos registros
|
||||
La información del límite de tasa se incluye en los encabezados de respuesta:
|
||||
- `X-RateLimit-Limit`: Solicitudes por minuto (tasa de recarga)
|
||||
- `X-RateLimit-Remaining`: Tokens disponibles actualmente
|
||||
- `X-RateLimit-Reset`: Marca de tiempo ISO cuando los tokens se recargan nuevamente
|
||||
|
||||
## Ejemplo: Sondeo para nuevos registros
|
||||
|
||||
```javascript
|
||||
let cursor = null;
|
||||
|
||||
@@ -147,8 +147,20 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
{
|
||||
"success": true,
|
||||
"rateLimit": {
|
||||
"sync": { "isLimited": false, "limit": 10, "remaining": 10, "resetAt": "2025-09-08T22:51:55.999Z" },
|
||||
"async": { "isLimited": false, "limit": 50, "remaining": 50, "resetAt": "2025-09-08T22:51:56.155Z" },
|
||||
"sync": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 25,
|
||||
"maxBurst": 50,
|
||||
"remaining": 50,
|
||||
"resetAt": "2025-09-08T22:51:55.999Z"
|
||||
},
|
||||
"async": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 400,
|
||||
"resetAt": "2025-09-08T22:51:56.155Z"
|
||||
},
|
||||
"authType": "api"
|
||||
},
|
||||
"usage": {
|
||||
@@ -159,6 +171,11 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**Campos de límite de tasa:**
|
||||
- `requestsPerMinute`: Límite de tasa sostenida (los tokens se recargan a esta velocidad)
|
||||
- `maxBurst`: Máximo de tokens que puedes acumular (capacidad de ráfaga)
|
||||
- `remaining`: Tokens disponibles actualmente (puede ser hasta `maxBurst`)
|
||||
|
||||
**Campos de respuesta:**
|
||||
- `currentPeriodCost` refleja el uso en el período de facturación actual
|
||||
- `limit` se deriva de límites individuales (Gratuito/Pro) o límites agrupados de la organización (Equipo/Empresa)
|
||||
@@ -170,38 +187,38 @@ Los diferentes planes de suscripción tienen diferentes límites de uso:
|
||||
|
||||
| Plan | Límite de uso mensual | Límites de tasa (por minuto) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **Gratuito** | $10 | 5 sinc, 10 asinc |
|
||||
| **Pro** | $100 | 10 sinc, 50 asinc |
|
||||
| **Equipo** | $500 (agrupado) | 50 sinc, 100 asinc |
|
||||
| **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
|
||||
|
||||
Sim utiliza un modelo de facturación de **suscripción base + exceso**:
|
||||
Sim utiliza un modelo de facturación de **suscripción base + excedente**:
|
||||
|
||||
### Cómo funciona
|
||||
|
||||
**Plan Pro ($20/mes):**
|
||||
- La suscripción mensual incluye $20 de uso
|
||||
- Uso por debajo de $20 → Sin cargos adicionales
|
||||
- Uso por encima de $20 → Pagas el exceso al final del mes
|
||||
- Ejemplo: $35 de uso = $20 (suscripción) + $15 (exceso)
|
||||
- Uso por encima de $20 → Pagas el excedente al final del mes
|
||||
- Ejemplo: $35 de uso = $20 (suscripción) + $15 (excedente)
|
||||
|
||||
**Plan de equipo ($40/usuario/mes):**
|
||||
**Plan de Equipo ($40/usuario/mes):**
|
||||
- Uso agrupado entre todos los miembros del equipo
|
||||
- Exceso calculado del uso total del equipo
|
||||
- Excedente calculado del uso total del equipo
|
||||
- El propietario de la organización recibe una sola factura
|
||||
|
||||
**Planes empresariales:**
|
||||
- Precio mensual fijo, sin excesos
|
||||
**Planes Empresariales:**
|
||||
- Precio mensual fijo, sin excedentes
|
||||
- Límites de uso personalizados según el acuerdo
|
||||
|
||||
### Facturación por umbral
|
||||
|
||||
Cuando el exceso no facturado alcanza los $50, Sim factura automáticamente el monto total no facturado.
|
||||
Cuando el excedente no facturado alcanza los $50, Sim factura automáticamente el monto total no facturado.
|
||||
|
||||
**Ejemplo:**
|
||||
- Día 10: $70 de exceso → Factura inmediata de $70
|
||||
- Día 10: $70 de excedente → Factura inmediata de $70
|
||||
- Día 15: $35 adicionales de uso ($105 en total) → Ya facturado, sin acción
|
||||
- Día 20: Otros $50 de uso ($155 en total, $85 no facturados) → Factura inmediata de $85
|
||||
|
||||
@@ -209,8 +226,8 @@ Esto distribuye los cargos por exceso a lo largo del mes en lugar de una gran fa
|
||||
|
||||
## Mejores prácticas para la gestión de costos
|
||||
|
||||
1. **Monitorear regularmente**: Revisa tu panel de uso frecuentemente para evitar sorpresas
|
||||
2. **Establecer presupuestos**: Usa los límites del plan como guías para tu gasto
|
||||
1. **Monitorear regularmente**: Revisa tu panel de uso con frecuencia para evitar sorpresas
|
||||
2. **Establecer presupuestos**: Utiliza los límites del plan como guías para tu gasto
|
||||
3. **Optimizar flujos de trabajo**: Revisa las ejecuciones de alto costo y optimiza los prompts o la selección de modelos
|
||||
4. **Usar modelos apropiados**: Ajusta la complejidad del modelo a los requisitos de la tarea
|
||||
5. **Agrupar tareas similares**: Combina múltiples solicitudes cuando sea posible para reducir la sobrecarga
|
||||
|
||||
@@ -49,8 +49,8 @@ Obtener una lista de mercados de predicción de Kalshi con filtrado opcional
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de mercados y metadatos |
|
||||
| `markets` | array | Array de objetos de mercado |
|
||||
| `paging` | object | Cursor de paginación para obtener más resultados |
|
||||
|
||||
### `kalshi_get_market`
|
||||
|
||||
@@ -66,8 +66,7 @@ Obtener detalles de un mercado de predicción específico por ticker
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del mercado y metadatos |
|
||||
| `market` | object | Objeto de mercado con detalles |
|
||||
|
||||
### `kalshi_get_events`
|
||||
|
||||
@@ -87,8 +86,8 @@ Obtener una lista de eventos de Kalshi con filtrado opcional
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos de eventos |
|
||||
| `events` | array | Array de objetos de eventos |
|
||||
| `paging` | object | Cursor de paginación para obtener más resultados |
|
||||
|
||||
### `kalshi_get_event`
|
||||
|
||||
@@ -105,8 +104,7 @@ Recuperar detalles de un evento específico por ticker
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos del evento |
|
||||
| `event` | object | Objeto de evento con detalles |
|
||||
|
||||
### `kalshi_get_balance`
|
||||
|
||||
@@ -123,8 +121,10 @@ Recuperar el saldo de tu cuenta y el valor de la cartera desde Kalshi
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos del saldo |
|
||||
| `balance` | number | Saldo de la cuenta en centavos |
|
||||
| `portfolioValue` | number | Valor de la cartera en centavos |
|
||||
| `balanceDollars` | number | Saldo de la cuenta en dólares |
|
||||
| `portfolioValueDollars` | number | Valor de la cartera en dólares |
|
||||
|
||||
### `kalshi_get_positions`
|
||||
|
||||
@@ -146,8 +146,8 @@ Recuperar tus posiciones abiertas desde Kalshi
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de posiciones y metadatos |
|
||||
| `positions` | array | Array de objetos de posición |
|
||||
| `paging` | object | Cursor de paginación para obtener más resultados |
|
||||
|
||||
### `kalshi_get_orders`
|
||||
|
||||
@@ -169,8 +169,8 @@ Recupera tus órdenes de Kalshi con filtrado opcional
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de órdenes y metadatos |
|
||||
| `orders` | array | Array de objetos de órdenes |
|
||||
| `paging` | object | Cursor de paginación para obtener más resultados |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
@@ -188,8 +188,7 @@ Recupera detalles de una orden específica por ID desde Kalshi
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden |
|
||||
| `order` | object | Objeto de orden con detalles |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
@@ -205,8 +204,7 @@ Recupera el libro de órdenes (ofertas de sí y no) para un mercado específico
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del libro de órdenes y metadatos |
|
||||
| `orderbook` | object | Libro de órdenes con ofertas y demandas de sí/no |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
@@ -223,8 +221,8 @@ Recupera operaciones recientes de todos los mercados
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de operaciones y metadatos |
|
||||
| `trades` | array | Array de objetos de operaciones |
|
||||
| `paging` | object | Cursor de paginación para obtener más resultados |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
@@ -244,8 +242,7 @@ Obtener datos de velas OHLC para un mercado específico
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de velas y metadatos |
|
||||
| `candlesticks` | array | Array de datos de velas OHLC |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -268,8 +265,8 @@ Recuperar tu portafolio
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de ejecuciones y metadatos |
|
||||
| `fills` | array | Array de objetos de ejecuciones/operaciones |
|
||||
| `paging` | object | Cursor de paginación para obtener más resultados |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
@@ -285,8 +282,7 @@ Obtener detalles de una serie de mercado específica por ticker
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la serie y metadatos |
|
||||
| `series` | object | Objeto de serie con detalles |
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
@@ -301,8 +297,7 @@ Obtener el estado actual del intercambio Kalshi (actividad de trading y del inte
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del estado del intercambio y metadatos |
|
||||
| `status` | object | Estado del exchange con indicadores trading_active y exchange_active |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
@@ -336,8 +331,7 @@ Crear una nueva orden en un mercado de predicción de Kalshi
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden creada |
|
||||
| `order` | object | El objeto de orden creada |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
@@ -355,8 +349,8 @@ Cancelar una orden existente en Kalshi
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden cancelada |
|
||||
| `order` | object | El objeto de orden cancelada |
|
||||
| `reducedBy` | number | Número de contratos cancelados |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
@@ -384,8 +378,7 @@ Modificar el precio o la cantidad de una orden existente en Kalshi
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de la orden modificada |
|
||||
| `order` | object | El objeto de orden modificada |
|
||||
|
||||
## Notas
|
||||
|
||||
|
||||
@@ -38,21 +38,20 @@ Obtener una lista de mercados de predicción de Polymarket con filtrado opcional
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filtrar por estado cerrado \(true/false\). Use false para mostrar solo mercados activos. |
|
||||
| `order` | string | No | Campo de ordenación \(p. ej., volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | No | Dirección de ordenación \(true para ascendente, false para descendente\) |
|
||||
| `tagId` | string | No | Filtrar por ID de etiqueta |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
| `limit` | string | No | Número de resultados por página \(máximo 50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación \(omitir esta cantidad de resultados\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de mercados y metadatos |
|
||||
| `markets` | array | Array de objetos de mercado |
|
||||
|
||||
### `polymarket_get_market`
|
||||
|
||||
@@ -69,8 +68,7 @@ Obtener detalles de un mercado de predicción específico por ID o slug
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de mercado y metadatos |
|
||||
| `market` | object | Objeto de mercado con detalles |
|
||||
|
||||
### `polymarket_get_events`
|
||||
|
||||
@@ -78,21 +76,20 @@ Obtener una lista de eventos de Polymarket con filtrado opcional
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | No | Filtrar por estado cerrado \(true/false\). Use false para mostrar solo eventos activos. |
|
||||
| `order` | string | No | Campo de ordenación \(p. ej., volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | No | Dirección de ordenación \(true para ascendente, false para descendente\) |
|
||||
| `tagId` | string | No | Filtrar por ID de etiqueta |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
| `limit` | string | No | Número de resultados por página \(máximo 50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación \(omitir esta cantidad de resultados\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de eventos y metadatos |
|
||||
| `events` | array | Array de objetos de eventos |
|
||||
|
||||
### `polymarket_get_event`
|
||||
|
||||
@@ -109,8 +106,7 @@ Obtener detalles de un evento específico por ID o slug
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos del evento |
|
||||
| `event` | object | Objeto de evento con detalles |
|
||||
|
||||
### `polymarket_get_tags`
|
||||
|
||||
@@ -120,15 +116,14 @@ Obtener etiquetas disponibles para filtrar mercados de Polymarket
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
| `limit` | string | No | Número de resultados por página \(máx 50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación \(omitir esta cantidad de resultados\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos de etiquetas |
|
||||
| `tags` | array | Array de objetos de etiquetas con id, etiqueta y slug |
|
||||
|
||||
### `polymarket_search`
|
||||
|
||||
@@ -139,15 +134,14 @@ Buscar mercados, eventos y perfiles en Polymarket
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | Sí | Término de búsqueda |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación \(omitir esta cantidad de resultados\) |
|
||||
| `limit` | string | No | Número de resultados por página \(máx 50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Resultados de búsqueda y metadatos |
|
||||
| `results` | object | Resultados de búsqueda que contienen arrays de mercados, eventos y perfiles |
|
||||
|
||||
### `polymarket_get_series`
|
||||
|
||||
@@ -157,7 +151,7 @@ Obtener series (grupos de mercados relacionados) de Polymarket
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
| `limit` | string | No | Número de resultados por página \(máx 50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación \(omitir esta cantidad de resultados\) |
|
||||
|
||||
#### Salida
|
||||
@@ -165,7 +159,7 @@ Obtener series (grupos de mercados relacionados) de Polymarket
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos de la serie |
|
||||
| `output` | object | Datos de series y metadatos |
|
||||
|
||||
### `polymarket_get_series_by_id`
|
||||
|
||||
@@ -182,7 +176,7 @@ Recuperar una serie específica (grupo de mercado relacionado) por ID desde Poly
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos de la serie |
|
||||
| `output` | object | Datos de serie y metadatos |
|
||||
|
||||
### `polymarket_get_orderbook`
|
||||
|
||||
@@ -199,7 +193,7 @@ Recuperar el resumen del libro de órdenes para un token específico
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos y metadatos del libro de órdenes |
|
||||
| `output` | object | Datos del libro de órdenes y metadatos |
|
||||
|
||||
### `polymarket_get_price`
|
||||
|
||||
@@ -217,7 +211,7 @@ Recuperar el precio de mercado para un token y lado específicos
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de precio y metadatos |
|
||||
| `output` | object | Datos de precio de mercado y metadatos |
|
||||
|
||||
### `polymarket_get_midpoint`
|
||||
|
||||
@@ -234,7 +228,7 @@ Recuperar el precio medio para un token específico
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del precio medio y metadatos |
|
||||
| `output` | object | Datos de precio medio y metadatos |
|
||||
|
||||
### `polymarket_get_price_history`
|
||||
|
||||
@@ -272,7 +266,7 @@ Recuperar el último precio de operación para un token específico
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Último precio de operación y metadatos |
|
||||
| `output` | object | Datos del último precio de operación y metadatos |
|
||||
|
||||
### `polymarket_get_spread`
|
||||
|
||||
@@ -289,7 +283,7 @@ Recuperar el diferencial de oferta y demanda para un token específico
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos del diferencial y metadatos |
|
||||
| `output` | object | Datos del diferencial de compra-venta y metadatos |
|
||||
|
||||
### `polymarket_get_tick_size`
|
||||
|
||||
@@ -306,7 +300,7 @@ Recuperar el tamaño mínimo de tick para un token específico
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Tamaño del tick y metadatos |
|
||||
| `output` | object | Datos del tamaño mínimo de tick y metadatos |
|
||||
|
||||
### `polymarket_get_positions`
|
||||
|
||||
@@ -332,19 +326,18 @@ Recuperar historial de operaciones de Polymarket
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `user` | string | No | Dirección de la cartera del usuario para filtrar operaciones |
|
||||
| `market` | string | No | ID de mercado para filtrar operaciones |
|
||||
| `limit` | string | No | Número de resultados por página \(recomendado: 25-50\) |
|
||||
| `limit` | string | No | Número de resultados por página \(máximo 50\) |
|
||||
| `offset` | string | No | Desplazamiento de paginación \(omitir esta cantidad de resultados\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `output` | object | Datos de operaciones y metadatos |
|
||||
| `trades` | array | Array de objetos de operaciones |
|
||||
|
||||
## Notas
|
||||
|
||||
|
||||
@@ -343,19 +343,30 @@ Eliminar una plantilla de correo electrónico de SendGrid
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Estado de éxito de la operación |
|
||||
| `message` | string | Mensaje de estado o éxito |
|
||||
| `messageId` | string | ID del mensaje de correo electrónico \(send_mail\) |
|
||||
| `to` | string | Dirección de correo electrónico del destinatario \(send_mail\) |
|
||||
| `subject` | string | Asunto del correo electrónico \(send_mail, create_template_version\) |
|
||||
| `id` | string | ID del recurso |
|
||||
| `jobId` | string | ID del trabajo para operaciones asíncronas |
|
||||
| `email` | string | Dirección de correo electrónico |
|
||||
| `firstName` | string | Nombre |
|
||||
| `lastName` | string | Apellido |
|
||||
| `jobId` | string | ID de trabajo para operaciones asíncronas |
|
||||
| `email` | string | Dirección de correo electrónico del contacto |
|
||||
| `firstName` | string | Nombre del contacto |
|
||||
| `lastName` | string | Apellido del contacto |
|
||||
| `createdAt` | string | Marca de tiempo de creación |
|
||||
| `updatedAt` | string | Marca de tiempo de última actualización |
|
||||
| `listIds` | json | Array de IDs de listas a las que pertenece el contacto |
|
||||
| `customFields` | json | Valores de campos personalizados |
|
||||
| `contacts` | json | Array de contactos |
|
||||
| `contactCount` | number | Número de contactos |
|
||||
| `lists` | json | Array de listas |
|
||||
| `templates` | json | Array de plantillas |
|
||||
| `message` | string | Estado o mensaje de éxito |
|
||||
| `name` | string | Nombre del recurso |
|
||||
| `templates` | json | Array de plantillas |
|
||||
| `generation` | string | Generación de plantilla |
|
||||
| `versions` | json | Array de versiones de plantilla |
|
||||
| `templateId` | string | ID de plantilla |
|
||||
| `active` | boolean | Si la versión de la plantilla está activa |
|
||||
| `htmlContent` | string | Contenido HTML |
|
||||
| `plainContent` | string | Contenido de texto plano |
|
||||
|
||||
### `sendgrid_create_template_version`
|
||||
|
||||
|
||||
184
apps/docs/content/docs/es/tools/sftp.mdx
Normal file
184
apps/docs/content/docs/es/tools/sftp.mdx
Normal file
@@ -0,0 +1,184 @@
|
||||
---
|
||||
title: SFTP
|
||||
description: Transferir archivos a través de SFTP (Protocolo de transferencia de
|
||||
archivos SSH)
|
||||
---
|
||||
|
||||
import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="sftp"
|
||||
color="#2D3748"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
[SFTP (Protocolo de transferencia de archivos SSH)](https://en.wikipedia.org/wiki/SSH_File_Transfer_Protocol) es un protocolo de red seguro que te permite subir, descargar y gestionar archivos en servidores remotos. SFTP opera sobre SSH, lo que lo hace ideal para transferencias de archivos automatizadas y cifradas, así como para la gestión remota de archivos dentro de flujos de trabajo modernos.
|
||||
|
||||
Con las herramientas SFTP integradas en Sim, puedes automatizar fácilmente el movimiento de archivos entre tus agentes de IA y sistemas o servidores externos. Esto permite a tus agentes gestionar intercambios críticos de datos, copias de seguridad, generación de documentos y orquestación de sistemas remotos, todo con una seguridad robusta.
|
||||
|
||||
**Funcionalidades clave disponibles a través de las herramientas SFTP:**
|
||||
|
||||
- **Subir archivos:** Transfiere sin problemas archivos de cualquier tipo desde tu flujo de trabajo a un servidor remoto, con soporte tanto para autenticación por contraseña como por clave privada SSH.
|
||||
- **Descargar archivos:** Recupera archivos de servidores SFTP remotos directamente para su procesamiento, archivo o automatización adicional.
|
||||
- **Listar y gestionar archivos:** Enumera directorios, elimina o crea archivos y carpetas, y gestiona permisos del sistema de archivos de forma remota.
|
||||
- **Autenticación flexible:** Conéctate usando contraseñas tradicionales o claves SSH, con soporte para frases de contraseña y control de permisos.
|
||||
- **Soporte para archivos grandes:** Gestiona programáticamente cargas y descargas de archivos grandes, con límites de tamaño incorporados para mayor seguridad.
|
||||
|
||||
Al integrar SFTP en Sim, puedes automatizar operaciones seguras de archivos como parte de cualquier flujo de trabajo, ya sea recopilación de datos, informes, mantenimiento de sistemas remotos o intercambio dinámico de contenido entre plataformas.
|
||||
|
||||
Las secciones a continuación describen las principales herramientas SFTP disponibles:
|
||||
|
||||
- **sftp_upload:** Sube uno o más archivos a un servidor remoto.
|
||||
- **sftp_download:** Descarga archivos desde un servidor remoto a tu flujo de trabajo.
|
||||
- **sftp_list:** Lista el contenido de directorios en un servidor SFTP remoto.
|
||||
- **sftp_delete:** Elimina archivos o directorios de un servidor remoto.
|
||||
- **sftp_create:** Crea nuevos archivos en un servidor SFTP remoto.
|
||||
- **sftp_mkdir:** Crea nuevos directorios de forma remota.
|
||||
|
||||
Consulta la documentación de la herramienta a continuación para conocer los parámetros detallados de entrada y salida para cada operación.
|
||||
{/* MANUAL-CONTENT-END */}
|
||||
|
||||
## Instrucciones de uso
|
||||
|
||||
Sube, descarga, lista y gestiona archivos en servidores remotos a través de SFTP. Compatible con autenticación por contraseña y clave privada para transferencias seguras de archivos.
|
||||
|
||||
## Herramientas
|
||||
|
||||
### `sftp_upload`
|
||||
|
||||
Subir archivos a un servidor SFTP remoto
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `host` | string | Sí | Nombre de host o dirección IP del servidor SFTP |
|
||||
| `port` | number | Sí | Puerto del servidor SFTP \(predeterminado: 22\) |
|
||||
| `username` | string | Sí | Nombre de usuario SFTP |
|
||||
| `password` | string | No | Contraseña para autenticación \(si no se usa clave privada\) |
|
||||
| `privateKey` | string | No | Clave privada para autenticación \(formato OpenSSH\) |
|
||||
| `passphrase` | string | No | Frase de contraseña para clave privada cifrada |
|
||||
| `remotePath` | string | Sí | Directorio de destino en el servidor remoto |
|
||||
| `files` | file[] | No | Archivos para subir |
|
||||
| `fileContent` | string | No | Contenido directo del archivo para subir \(para archivos de texto\) |
|
||||
| `fileName` | string | No | Nombre del archivo cuando se usa contenido directo |
|
||||
| `overwrite` | boolean | No | Si se deben sobrescribir archivos existentes \(predeterminado: true\) |
|
||||
| `permissions` | string | No | Permisos del archivo \(p. ej., 0644\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Si la subida fue exitosa |
|
||||
| `uploadedFiles` | json | Array de detalles de archivos subidos \(nombre, rutaRemota, tamaño\) |
|
||||
| `message` | string | Mensaje de estado de la operación |
|
||||
|
||||
### `sftp_download`
|
||||
|
||||
Descargar un archivo desde un servidor SFTP remoto
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Sí | Nombre de host o dirección IP del servidor SFTP |
|
||||
| `port` | number | Sí | Puerto del servidor SFTP \(predeterminado: 22\) |
|
||||
| `username` | string | Sí | Nombre de usuario SFTP |
|
||||
| `password` | string | No | Contraseña para autenticación \(si no se usa clave privada\) |
|
||||
| `privateKey` | string | No | Clave privada para autenticación \(formato OpenSSH\) |
|
||||
| `passphrase` | string | No | Frase de contraseña para clave privada cifrada |
|
||||
| `remotePath` | string | Sí | Ruta al archivo en el servidor remoto |
|
||||
| `encoding` | string | No | Codificación de salida: utf-8 para texto, base64 para binario \(predeterminado: utf-8\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Si la descarga fue exitosa |
|
||||
| `fileName` | string | Nombre del archivo descargado |
|
||||
| `content` | string | Contenido del archivo \(texto o codificado en base64\) |
|
||||
| `size` | number | Tamaño del archivo en bytes |
|
||||
| `encoding` | string | Codificación del contenido \(utf-8 o base64\) |
|
||||
| `message` | string | Mensaje de estado de la operación |
|
||||
|
||||
### `sftp_list`
|
||||
|
||||
Listar archivos y directorios en un servidor SFTP remoto
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Sí | Nombre de host o dirección IP del servidor SFTP |
|
||||
| `port` | number | Sí | Puerto del servidor SFTP \(predeterminado: 22\) |
|
||||
| `username` | string | Sí | Nombre de usuario SFTP |
|
||||
| `password` | string | No | Contraseña para autenticación \(si no se usa clave privada\) |
|
||||
| `privateKey` | string | No | Clave privada para autenticación \(formato OpenSSH\) |
|
||||
| `passphrase` | string | No | Frase de contraseña para clave privada cifrada |
|
||||
| `remotePath` | string | Sí | Ruta del directorio en el servidor remoto |
|
||||
| `detailed` | boolean | No | Incluir información detallada de archivos \(tamaño, permisos, fecha de modificación\) |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Si la operación fue exitosa |
|
||||
| `path` | string | Ruta del directorio que fue listado |
|
||||
| `entries` | json | Array de entradas del directorio con nombre, tipo, tamaño, permisos, modifiedAt |
|
||||
| `count` | number | Número de entradas en el directorio |
|
||||
| `message` | string | Mensaje de estado de la operación |
|
||||
|
||||
### `sftp_delete`
|
||||
|
||||
Eliminar un archivo o directorio en un servidor SFTP remoto
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Requerido | Descripción |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `host` | string | Sí | Nombre de host o dirección IP del servidor SFTP |
|
||||
| `port` | number | Sí | Puerto del servidor SFTP \(predeterminado: 22\) |
|
||||
| `username` | string | Sí | Nombre de usuario SFTP |
|
||||
| `password` | string | No | Contraseña para autenticación \(si no se usa clave privada\) |
|
||||
| `privateKey` | string | No | Clave privada para autenticación \(formato OpenSSH\) |
|
||||
| `passphrase` | string | No | Frase de contraseña para clave privada cifrada |
|
||||
| `remotePath` | string | Sí | Ruta al archivo o directorio a eliminar |
|
||||
| `recursive` | boolean | No | Eliminar directorios recursivamente |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Si la eliminación fue exitosa |
|
||||
| `deletedPath` | string | Ruta que fue eliminada |
|
||||
| `message` | string | Mensaje de estado de la operación |
|
||||
|
||||
### `sftp_mkdir`
|
||||
|
||||
Crear un directorio en un servidor SFTP remoto
|
||||
|
||||
#### Entrada
|
||||
|
||||
| Parámetro | Tipo | Obligatorio | Descripción |
|
||||
| --------- | ---- | ----------- | ----------- |
|
||||
| `host` | string | Sí | Nombre de host o dirección IP del servidor SFTP |
|
||||
| `port` | number | Sí | Puerto del servidor SFTP \(predeterminado: 22\) |
|
||||
| `username` | string | Sí | Nombre de usuario SFTP |
|
||||
| `password` | string | No | Contraseña para autenticación \(si no se usa clave privada\) |
|
||||
| `privateKey` | string | No | Clave privada para autenticación \(formato OpenSSH\) |
|
||||
| `passphrase` | string | No | Frase de contraseña para clave privada cifrada |
|
||||
| `remotePath` | string | Sí | Ruta para el nuevo directorio |
|
||||
| `recursive` | boolean | No | Crear directorios principales si no existen |
|
||||
|
||||
#### Salida
|
||||
|
||||
| Parámetro | Tipo | Descripción |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Si el directorio se creó correctamente |
|
||||
| `createdPath` | string | Ruta del directorio creado |
|
||||
| `message` | string | Mensaje de estado de la operación |
|
||||
|
||||
## Notas
|
||||
|
||||
- Categoría: `tools`
|
||||
- Tipo: `sftp`
|
||||
@@ -30,6 +30,9 @@ Utiliza el bloque Start para todo lo que se origina desde el editor, despliegue
|
||||
<Card title="Schedule" href="/triggers/schedule">
|
||||
Ejecución basada en cron o intervalos
|
||||
</Card>
|
||||
<Card title="RSS Feed" href="/triggers/rss">
|
||||
Monitorea feeds RSS y Atom para nuevo contenido
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Comparación rápida
|
||||
@@ -39,6 +42,7 @@ Utiliza el bloque Start para todo lo que se origina desde el editor, despliegue
|
||||
| **Start** | Ejecuciones del editor, solicitudes de despliegue a API o mensajes de chat |
|
||||
| **Schedule** | Temporizador gestionado en el bloque de programación |
|
||||
| **Webhook** | Al recibir una solicitud HTTP entrante |
|
||||
| **RSS Feed** | Nuevo elemento publicado en el feed |
|
||||
|
||||
> El bloque Start siempre expone los campos `input`, `conversationId` y `files`. Añade campos personalizados al formato de entrada para datos estructurados adicionales.
|
||||
|
||||
|
||||
49
apps/docs/content/docs/es/triggers/rss.mdx
Normal file
49
apps/docs/content/docs/es/triggers/rss.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Feed RSS
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
El bloque de Feed RSS monitorea feeds RSS y Atom – cuando se publican nuevos elementos, tu flujo de trabajo se activa automáticamente.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/rss.png"
|
||||
alt="Bloque de Feed RSS"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Configuración
|
||||
|
||||
1. **Añadir bloque de Feed RSS** - Arrastra el bloque de Feed RSS para iniciar tu flujo de trabajo
|
||||
2. **Introducir URL del feed** - Pega la URL de cualquier feed RSS o Atom
|
||||
3. **Implementar** - Implementa tu flujo de trabajo para activar el sondeo
|
||||
|
||||
Una vez implementado, el feed se comprueba cada minuto en busca de nuevos elementos.
|
||||
|
||||
## Campos de salida
|
||||
|
||||
| Campo | Tipo | Descripción |
|
||||
|-------|------|-------------|
|
||||
| `title` | string | Título del elemento |
|
||||
| `link` | string | Enlace del elemento |
|
||||
| `pubDate` | string | Fecha de publicación |
|
||||
| `item` | object | Elemento en bruto con todos los campos |
|
||||
| `feed` | object | Metadatos en bruto del feed |
|
||||
|
||||
Accede a los campos mapeados directamente (`<rss.title>`) o utiliza los objetos en bruto para cualquier campo (`<rss.item.author>`, `<rss.feed.language>`).
|
||||
|
||||
## Casos de uso
|
||||
|
||||
- **Monitoreo de contenido** - Sigue blogs, sitios de noticias o actualizaciones de competidores
|
||||
- **Automatización de podcasts** - Activa flujos de trabajo cuando se publican nuevos episodios
|
||||
- **Seguimiento de lanzamientos** - Monitorea lanzamientos de GitHub, registros de cambios o actualizaciones de productos
|
||||
- **Agregación social** - Recopila contenido de plataformas que exponen feeds RSS
|
||||
|
||||
<Callout>
|
||||
Los disparadores RSS solo se activan para elementos publicados después de guardar el disparador. Los elementos existentes en el feed no se procesan.
|
||||
</Callout>
|
||||
@@ -27,14 +27,16 @@ Toutes les réponses API incluent des informations sur vos limites d'exécution
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60, // Max sync workflow executions per minute
|
||||
"remaining": 58, // Remaining sync workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 60, // Sustained rate limit per minute
|
||||
"maxBurst": 120, // Maximum burst capacity
|
||||
"remaining": 118, // Current tokens available (up to maxBurst)
|
||||
"resetAt": "..." // When tokens next refill
|
||||
},
|
||||
"async": {
|
||||
"limit": 60, // Max async workflow executions per minute
|
||||
"remaining": 59, // Remaining async workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 200, // Sustained rate limit per minute
|
||||
"maxBurst": 400, // Maximum burst capacity
|
||||
"remaining": 398, // Current tokens available
|
||||
"resetAt": "..." // When tokens next refill
|
||||
}
|
||||
},
|
||||
"usage": {
|
||||
@@ -46,7 +48,7 @@ Toutes les réponses API incluent des informations sur vos limites d'exécution
|
||||
}
|
||||
```
|
||||
|
||||
**Remarque :** Les limites de débit dans le corps de la réponse concernent les exécutions de workflow. Les limites de débit pour l'appel de ce point de terminaison API se trouvent dans les en-têtes de réponse (`X-RateLimit-*`).
|
||||
**Remarque :** les limites de débit utilisent un algorithme de seau à jetons. `remaining` peut dépasser `requestsPerMinute` jusqu'à `maxBurst` lorsque vous n'avez pas utilisé récemment votre allocation complète, permettant ainsi un trafic en rafale. Les limites de débit dans le corps de la réponse concernent les exécutions de workflow. Les limites de débit pour appeler ce point de terminaison API se trouvent dans les en-têtes de réponse (`X-RateLimit-*`).
|
||||
|
||||
### Interrogation des journaux
|
||||
|
||||
@@ -110,13 +112,15 @@ Interrogez les journaux d'exécution des workflows avec de nombreuses options de
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -190,13 +194,15 @@ Récupérer des informations détaillées sur une entrée de journal spécifique
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -482,19 +488,27 @@ Les livraisons de webhook échouées sont réessayées avec un backoff exponenti
|
||||
|
||||
## Limitation de débit
|
||||
|
||||
L'API implémente une limitation de débit pour garantir une utilisation équitable :
|
||||
L'API utilise un **algorithme de seau à jetons** pour limiter le débit, offrant une utilisation équitable tout en permettant des pics de trafic :
|
||||
|
||||
- **Plan gratuit** : 10 requêtes par minute
|
||||
- **Plan Pro** : 30 requêtes par minute
|
||||
- **Plan Équipe** : 60 requêtes par minute
|
||||
- **Plan Entreprise** : Limites personnalisées
|
||||
| Forfait | Requêtes/minute | Capacité de rafale |
|
||||
|------|-----------------|----------------|
|
||||
| Gratuit | 10 | 20 |
|
||||
| Pro | 30 | 60 |
|
||||
| Équipe | 60 | 120 |
|
||||
| Entreprise | 120 | 240 |
|
||||
|
||||
Les informations de limitation de débit sont incluses dans les en-têtes de réponse :
|
||||
- `X-RateLimit-Limit` : Nombre maximum de requêtes par fenêtre
|
||||
- `X-RateLimit-Remaining` : Requêtes restantes dans la fenêtre actuelle
|
||||
- `X-RateLimit-Reset` : Horodatage ISO indiquant quand la fenêtre se réinitialise
|
||||
**Comment ça fonctionne :**
|
||||
- Les jetons se rechargent au rythme de `requestsPerMinute`
|
||||
- Vous pouvez accumuler jusqu'à `maxBurst` jetons en période d'inactivité
|
||||
- Chaque requête consomme 1 jeton
|
||||
- La capacité de rafale permet de gérer les pics de trafic
|
||||
|
||||
## Exemple : Polling pour nouveaux logs
|
||||
Les informations sur les limites de débit sont incluses dans les en-têtes de réponse :
|
||||
- `X-RateLimit-Limit` : requêtes par minute (taux de recharge)
|
||||
- `X-RateLimit-Remaining` : jetons actuellement disponibles
|
||||
- `X-RateLimit-Reset` : horodatage ISO indiquant quand les jetons seront rechargés
|
||||
|
||||
## Exemple : interrogation pour de nouveaux journaux
|
||||
|
||||
```javascript
|
||||
let cursor = null;
|
||||
@@ -541,7 +555,7 @@ async function pollLogs() {
|
||||
setInterval(pollLogs, 30000);
|
||||
```
|
||||
|
||||
## Exemple : Traitement des webhooks
|
||||
## Exemple : traitement des webhooks
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
|
||||
@@ -147,8 +147,20 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
{
|
||||
"success": true,
|
||||
"rateLimit": {
|
||||
"sync": { "isLimited": false, "limit": 10, "remaining": 10, "resetAt": "2025-09-08T22:51:55.999Z" },
|
||||
"async": { "isLimited": false, "limit": 50, "remaining": 50, "resetAt": "2025-09-08T22:51:56.155Z" },
|
||||
"sync": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 25,
|
||||
"maxBurst": 50,
|
||||
"remaining": 50,
|
||||
"resetAt": "2025-09-08T22:51:55.999Z"
|
||||
},
|
||||
"async": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 400,
|
||||
"resetAt": "2025-09-08T22:51:56.155Z"
|
||||
},
|
||||
"authType": "api"
|
||||
},
|
||||
"usage": {
|
||||
@@ -159,6 +171,11 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**Champs de limite de débit :**
|
||||
- `requestsPerMinute` : limite de débit soutenu (les jetons se rechargent à ce rythme)
|
||||
- `maxBurst` : nombre maximum de jetons que vous pouvez accumuler (capacité de rafale)
|
||||
- `remaining` : jetons actuellement disponibles (peut aller jusqu'à `maxBurst`)
|
||||
|
||||
**Champs de réponse :**
|
||||
- `currentPeriodCost` reflète l'utilisation dans la période de facturation actuelle
|
||||
- `limit` est dérivé des limites individuelles (Gratuit/Pro) ou des limites mutualisées de l'organisation (Équipe/Entreprise)
|
||||
@@ -188,7 +205,7 @@ Sim utilise un modèle de facturation **abonnement de base + dépassement** :
|
||||
- Exemple : 35 $ d'utilisation = 20 $ (abonnement) + 15 $ (dépassement)
|
||||
|
||||
**Forfait Équipe (40 $/siège/mois) :**
|
||||
- Utilisation mutualisée entre tous les membres de l'équipe
|
||||
- Utilisation mutualisée pour tous les membres de l'équipe
|
||||
- Dépassement calculé à partir de l'utilisation totale de l'équipe
|
||||
- Le propriétaire de l'organisation reçoit une seule facture
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ Récupérer une liste de marchés prédictifs de Kalshi avec filtrage optionnel
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Données des marchés et métadonnées |
|
||||
| `markets` | array | Tableau d'objets de marché |
|
||||
| `paging` | object | Curseur de pagination pour récupérer plus de résultats |
|
||||
|
||||
### `kalshi_get_market`
|
||||
|
||||
@@ -66,8 +66,7 @@ Récupérer les détails d'un marché prédictif spécifique par code
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Données du marché et métadonnées |
|
||||
| `market` | object | Objet de marché avec détails |
|
||||
|
||||
### `kalshi_get_events`
|
||||
|
||||
@@ -87,8 +86,8 @@ Récupérer une liste d'événements de Kalshi avec filtrage optionnel
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées des événements |
|
||||
| `events` | array | Tableau d'objets d'événement |
|
||||
| `paging` | object | Curseur de pagination pour récupérer plus de résultats |
|
||||
|
||||
### `kalshi_get_event`
|
||||
|
||||
@@ -105,8 +104,7 @@ Récupérer les détails d'un événement spécifique par ticker
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées de l'événement |
|
||||
| `event` | object | Objet d'événement avec détails |
|
||||
|
||||
### `kalshi_get_balance`
|
||||
|
||||
@@ -123,8 +121,10 @@ Récupérer le solde de votre compte et la valeur de votre portefeuille depuis K
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées du solde |
|
||||
| `balance` | number | Solde du compte en centimes |
|
||||
| `portfolioValue` | number | Valeur du portefeuille en centimes |
|
||||
| `balanceDollars` | number | Solde du compte en dollars |
|
||||
| `portfolioValueDollars` | number | Valeur du portefeuille en dollars |
|
||||
|
||||
### `kalshi_get_positions`
|
||||
|
||||
@@ -146,8 +146,8 @@ Récupérer vos positions ouvertes depuis Kalshi
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de positions et métadonnées |
|
||||
| `positions` | array | Tableau d'objets de position |
|
||||
| `paging` | object | Curseur de pagination pour récupérer plus de résultats |
|
||||
|
||||
### `kalshi_get_orders`
|
||||
|
||||
@@ -169,8 +169,8 @@ Récupérez vos ordres depuis Kalshi avec filtrage optionnel
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données des ordres et métadonnées |
|
||||
| `orders` | array | Tableau d'objets d'ordre |
|
||||
| `paging` | object | Curseur de pagination pour récupérer plus de résultats |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
@@ -188,8 +188,7 @@ Récupérer les détails d'un ordre spécifique par ID depuis Kalshi
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre |
|
||||
| `order` | object | Objet d'ordre avec détails |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
@@ -205,8 +204,7 @@ Récupérer le carnet d'ordres (offres oui et non) pour un marché spécifique
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données du carnet d'ordres et métadonnées |
|
||||
| `orderbook` | objet | Carnet d'ordres avec offres et demandes oui/non |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
@@ -223,8 +221,8 @@ Récupérer les transactions récentes sur tous les marchés
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données des transactions et métadonnées |
|
||||
| `trades` | tableau | Tableau d'objets de transactions |
|
||||
| `paging` | objet | Curseur de pagination pour récupérer plus de résultats |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
@@ -244,8 +242,7 @@ Récupérer les données de chandeliers OHLC pour un marché spécifique
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de chandeliers et métadonnées |
|
||||
| `candlesticks` | tableau | Tableau de données de chandeliers OHLC |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -268,8 +265,8 @@ Récupérer votre portefeuille
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées des exécutions |
|
||||
| `fills` | tableau | Tableau d'objets d'exécutions/transactions |
|
||||
| `paging` | objet | Curseur de pagination pour récupérer plus de résultats |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
@@ -285,8 +282,7 @@ Récupérer les détails d'une série de marché spécifique par ticker
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées de la série |
|
||||
| `series` | objet | Objet de série avec détails |
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
@@ -301,8 +297,7 @@ Récupérer le statut actuel de la plateforme d'échange Kalshi (activité de tr
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées du statut de l'échange |
|
||||
| `status` | objet | Statut de l'échange avec indicateurs trading_active et exchange_active |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
@@ -336,8 +331,7 @@ Créer un nouvel ordre sur un marché de prédiction Kalshi
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre créé |
|
||||
| `order` | objet | L'objet de l'ordre créé |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
@@ -355,8 +349,8 @@ Annuler un ordre existant sur Kalshi
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre annulé |
|
||||
| `order` | objet | L'objet de l'ordre annulé |
|
||||
| `reducedBy` | nombre | Nombre de contrats annulés |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
@@ -384,8 +378,7 @@ Modifier le prix ou la quantité d'un ordre existant sur Kalshi
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de l'ordre modifié |
|
||||
| `order` | objet | L'objet de l'ordre modifié |
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -40,19 +40,18 @@ Récupérer une liste des marchés prédictifs de Polymarket avec filtrage optio
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `closed` | string | Non | Filtrer par statut fermé \(true/false\). Utilisez false pour les marchés actifs uniquement. |
|
||||
| `order` | string | Non | Champ de tri \(par exemple, volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `closed` | string | Non | Filtrer par statut fermé \(true/false\). Utiliser false pour les marchés actifs uniquement. |
|
||||
| `order` | string | Non | Champ de tri \(ex. volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | Non | Direction de tri \(true pour ascendant, false pour descendant\) |
|
||||
| `tagId` | string | Non | Filtrer par ID de tag |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(max 50\) |
|
||||
| `offset` | string | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `output` | objet | Données des marchés et métadonnées |
|
||||
| `markets` | array | Tableau d'objets de marché |
|
||||
|
||||
### `polymarket_get_market`
|
||||
|
||||
@@ -69,8 +68,7 @@ Récupérer les détails d'un marché prédictif spécifique par ID ou slug
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de marché et métadonnées |
|
||||
| `market` | object | Objet de marché avec détails |
|
||||
|
||||
### `polymarket_get_events`
|
||||
|
||||
@@ -80,19 +78,18 @@ Récupérer une liste d'événements de Polymarket avec filtrage optionnel
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `closed` | string | Non | Filtrer par statut fermé \(true/false\). Utilisez false pour les événements actifs uniquement. |
|
||||
| `order` | string | Non | Champ de tri \(par exemple, volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `closed` | string | Non | Filtrer par statut fermé \(true/false\). Utiliser false pour les événements actifs uniquement. |
|
||||
| `order` | string | Non | Champ de tri \(ex. volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | Non | Direction de tri \(true pour ascendant, false pour descendant\) |
|
||||
| `tagId` | string | Non | Filtrer par ID de tag |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(max 50\) |
|
||||
| `offset` | string | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données d'événements et métadonnées |
|
||||
| `events` | array | Tableau d'objets d'événements |
|
||||
|
||||
### `polymarket_get_event`
|
||||
|
||||
@@ -109,8 +106,7 @@ Récupérer les détails d'un événement spécifique par ID ou slug
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées de l'événement |
|
||||
| `event` | object | Objet d'événement avec détails |
|
||||
|
||||
### `polymarket_get_tags`
|
||||
|
||||
@@ -119,16 +115,15 @@ Récupérer les tags disponibles pour filtrer les marchés sur Polymarket
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(max 50\) |
|
||||
| `offset` | string | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées des tags |
|
||||
| `tags` | array | Tableau d'objets de tags avec id, label et slug |
|
||||
|
||||
### `polymarket_search`
|
||||
|
||||
@@ -137,17 +132,16 @@ Rechercher des marchés, des événements et des profils sur Polymarket
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `query` | string | Oui | Terme de recherche |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| `offset` | string | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(max 50\) |
|
||||
| `offset` | string | Non | Décalage de pagination |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Résultats de recherche et métadonnées |
|
||||
| `results` | object | Résultats de recherche contenant des tableaux de marchés, d'événements et de profils |
|
||||
|
||||
### `polymarket_get_series`
|
||||
|
||||
@@ -156,8 +150,8 @@ Récupérer des séries (groupes de marchés liés) depuis Polymarket
|
||||
#### Entrée
|
||||
|
||||
| Paramètre | Type | Obligatoire | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(max 50\) |
|
||||
| `offset` | string | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
|
||||
#### Sortie
|
||||
@@ -165,7 +159,7 @@ Récupérer des séries (groupes de marchés liés) depuis Polymarket
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées de la série |
|
||||
| `output` | object | Données des séries et métadonnées |
|
||||
|
||||
### `polymarket_get_series_by_id`
|
||||
|
||||
@@ -182,7 +176,7 @@ Récupérer une série spécifique (groupe de marché associé) par ID depuis Po
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées de la série |
|
||||
| `output` | object | Données de série et métadonnées |
|
||||
|
||||
### `polymarket_get_orderbook`
|
||||
|
||||
@@ -199,7 +193,7 @@ Récupérer le résumé du carnet d'ordres pour un jeton spécifique
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données et métadonnées du carnet d'ordres |
|
||||
| `output` | object | Données du carnet d'ordres et métadonnées |
|
||||
|
||||
### `polymarket_get_price`
|
||||
|
||||
@@ -217,7 +211,7 @@ Récupérer le prix du marché pour un jeton et un côté spécifiques
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données de prix et métadonnées |
|
||||
| `output` | object | Données de prix du marché et métadonnées |
|
||||
|
||||
### `polymarket_get_midpoint`
|
||||
|
||||
@@ -255,7 +249,7 @@ Récupérer les données historiques de prix pour un jeton de marché spécifiqu
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données d'historique de prix et métadonnées |
|
||||
| `output` | object | Données d'historique des prix et métadonnées |
|
||||
|
||||
### `polymarket_get_last_trade_price`
|
||||
|
||||
@@ -272,7 +266,7 @@ Récupérer le dernier prix de transaction pour un jeton spécifique
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Dernier prix de transaction et métadonnées |
|
||||
| `output` | object | Données du dernier prix d'échange et métadonnées |
|
||||
|
||||
### `polymarket_get_spread`
|
||||
|
||||
@@ -289,7 +283,7 @@ Récupérer l'écart entre l'offre et la demande pour un jeton spécifique
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données d'écart et métadonnées |
|
||||
| `output` | object | Données d'écart achat-vente et métadonnées |
|
||||
|
||||
### `polymarket_get_tick_size`
|
||||
|
||||
@@ -306,7 +300,7 @@ Récupérer la taille minimale du tick pour un jeton spécifique
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Taille du tick et métadonnées |
|
||||
| `output` | object | Données de taille minimale de tick et métadonnées |
|
||||
|
||||
### `polymarket_get_positions`
|
||||
|
||||
@@ -336,15 +330,14 @@ Récupérer l'historique des transactions depuis Polymarket
|
||||
| --------- | ---- | ---------- | ----------- |
|
||||
| `user` | string | Non | Adresse du portefeuille de l'utilisateur pour filtrer les transactions |
|
||||
| `market` | string | Non | ID de marché pour filtrer les transactions |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(recommandé : 25-50\) |
|
||||
| `limit` | string | Non | Nombre de résultats par page \(max 50\) |
|
||||
| `offset` | string | Non | Décalage de pagination \(ignorer ce nombre de résultats\) |
|
||||
|
||||
#### Sortie
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `output` | object | Données des transactions et métadonnées |
|
||||
| `trades` | array | Tableau d'objets de transactions |
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -342,20 +342,31 @@ Supprimer un modèle d'e-mail de SendGrid
|
||||
|
||||
| Paramètre | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | booléen | Statut de réussite de l'opération |
|
||||
| `messageId` | chaîne | ID du message e-mail \(send_mail\) |
|
||||
| `id` | chaîne | ID de la ressource |
|
||||
| `jobId` | chaîne | ID de tâche pour les opérations asynchrones |
|
||||
| `email` | chaîne | Adresse e-mail |
|
||||
| `firstName` | chaîne | Prénom |
|
||||
| `lastName` | chaîne | Nom de famille |
|
||||
| `success` | boolean | Statut de réussite de l'opération |
|
||||
| `message` | string | Message d'état ou de réussite |
|
||||
| `messageId` | string | ID du message e-mail \(send_mail\) |
|
||||
| `to` | string | Adresse e-mail du destinataire \(send_mail\) |
|
||||
| `subject` | string | Objet de l'e-mail \(send_mail, create_template_version\) |
|
||||
| `id` | string | ID de la ressource |
|
||||
| `jobId` | string | ID de tâche pour les opérations asynchrones |
|
||||
| `email` | string | Adresse e-mail du contact |
|
||||
| `firstName` | string | Prénom du contact |
|
||||
| `lastName` | string | Nom de famille du contact |
|
||||
| `createdAt` | string | Horodatage de création |
|
||||
| `updatedAt` | string | Horodatage de dernière mise à jour |
|
||||
| `listIds` | json | Tableau des ID de listes auxquelles le contact appartient |
|
||||
| `customFields` | json | Valeurs des champs personnalisés |
|
||||
| `contacts` | json | Tableau de contacts |
|
||||
| `contactCount` | nombre | Nombre de contacts |
|
||||
| `contactCount` | number | Nombre de contacts |
|
||||
| `lists` | json | Tableau de listes |
|
||||
| `name` | string | Nom de la ressource |
|
||||
| `templates` | json | Tableau de modèles |
|
||||
| `message` | chaîne | Statut ou message de réussite |
|
||||
| `name` | chaîne | Nom de la ressource |
|
||||
| `generation` | chaîne | Génération du modèle |
|
||||
| `generation` | string | Génération du modèle |
|
||||
| `versions` | json | Tableau des versions du modèle |
|
||||
| `templateId` | string | ID du modèle |
|
||||
| `active` | boolean | Si la version du modèle est active |
|
||||
| `htmlContent` | string | Contenu HTML |
|
||||
| `plainContent` | string | Contenu en texte brut |
|
||||
|
||||
### `sendgrid_create_template_version`
|
||||
|
||||
|
||||
@@ -21,24 +21,28 @@ import { Image } from '@/components/ui/image'
|
||||
Utilisez le bloc Démarrer pour tout ce qui provient de l'éditeur, du déploiement vers l'API ou des expériences de déploiement vers le chat. D'autres déclencheurs restent disponibles pour les flux de travail basés sur des événements :
|
||||
|
||||
<Cards>
|
||||
<Card title="Démarrer" href="/triggers/start">
|
||||
<Card title="Start" href="/triggers/start">
|
||||
Point d'entrée unifié qui prend en charge les exécutions de l'éditeur, les déploiements d'API et les déploiements de chat
|
||||
</Card>
|
||||
<Card title="Webhook" href="/triggers/webhook">
|
||||
Recevoir des charges utiles de webhook externes
|
||||
</Card>
|
||||
<Card title="Planification" href="/triggers/schedule">
|
||||
<Card title="Schedule" href="/triggers/schedule">
|
||||
Exécution basée sur cron ou intervalle
|
||||
</Card>
|
||||
<Card title="RSS Feed" href="/triggers/rss">
|
||||
Surveiller les flux RSS et Atom pour du nouveau contenu
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## Comparaison rapide
|
||||
|
||||
| Déclencheur | Condition de démarrage |
|
||||
|---------|-----------------|
|
||||
| **Démarrer** | Exécutions de l'éditeur, requêtes de déploiement vers l'API ou messages de chat |
|
||||
| **Planification** | Minuteur géré dans le bloc de planification |
|
||||
| **Start** | Exécutions de l'éditeur, requêtes de déploiement d'API ou messages de chat |
|
||||
| **Schedule** | Minuteur géré dans le bloc de planification |
|
||||
| **Webhook** | Sur requête HTTP entrante |
|
||||
| **RSS Feed** | Nouvel élément publié dans le flux |
|
||||
|
||||
> Le bloc Démarrer expose toujours les champs `input`, `conversationId` et `files`. Ajoutez des champs personnalisés au format d'entrée pour des données structurées supplémentaires.
|
||||
|
||||
|
||||
49
apps/docs/content/docs/fr/triggers/rss.mdx
Normal file
49
apps/docs/content/docs/fr/triggers/rss.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Flux RSS
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
Le bloc Flux RSS surveille les flux RSS et Atom – lorsque de nouveaux éléments sont publiés, votre workflow se déclenche automatiquement.
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/rss.png"
|
||||
alt="Bloc Flux RSS"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## Configuration
|
||||
|
||||
1. **Ajouter le bloc Flux RSS** - Faites glisser le bloc Flux RSS pour démarrer votre workflow
|
||||
2. **Saisir l'URL du flux** - Collez l'URL de n'importe quel flux RSS ou Atom
|
||||
3. **Déployer** - Déployez votre workflow pour activer l'interrogation
|
||||
|
||||
Une fois déployé, le flux est vérifié chaque minute pour détecter de nouveaux éléments.
|
||||
|
||||
## Champs de sortie
|
||||
|
||||
| Champ | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `title` | string | Titre de l'élément |
|
||||
| `link` | string | Lien de l'élément |
|
||||
| `pubDate` | string | Date de publication |
|
||||
| `item` | object | Élément brut avec tous les champs |
|
||||
| `feed` | object | Métadonnées brutes du flux |
|
||||
|
||||
Accédez directement aux champs mappés (`<rss.title>`) ou utilisez les objets bruts pour n'importe quel champ (`<rss.item.author>`, `<rss.feed.language>`).
|
||||
|
||||
## Cas d'utilisation
|
||||
|
||||
- **Surveillance de contenu** - Suivez les blogs, sites d'actualités ou mises à jour des concurrents
|
||||
- **Automatisation de podcast** - Déclenchez des workflows lors de la sortie de nouveaux épisodes
|
||||
- **Suivi des versions** - Surveillez les versions GitHub, les journaux de modifications ou les mises à jour de produits
|
||||
- **Agrégation sociale** - Collectez du contenu à partir de plateformes qui exposent des flux RSS
|
||||
|
||||
<Callout>
|
||||
Les déclencheurs RSS ne s'activent que pour les éléments publiés après l'enregistrement du déclencheur. Les éléments existants du flux ne sont pas traités.
|
||||
</Callout>
|
||||
@@ -27,14 +27,16 @@ SimダッシュボードのユーザーセッティングからAPIキーを生
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60, // Max sync workflow executions per minute
|
||||
"remaining": 58, // Remaining sync workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 60, // Sustained rate limit per minute
|
||||
"maxBurst": 120, // Maximum burst capacity
|
||||
"remaining": 118, // Current tokens available (up to maxBurst)
|
||||
"resetAt": "..." // When tokens next refill
|
||||
},
|
||||
"async": {
|
||||
"limit": 60, // Max async workflow executions per minute
|
||||
"remaining": 59, // Remaining async workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 200, // Sustained rate limit per minute
|
||||
"maxBurst": 400, // Maximum burst capacity
|
||||
"remaining": 398, // Current tokens available
|
||||
"resetAt": "..." // When tokens next refill
|
||||
}
|
||||
},
|
||||
"usage": {
|
||||
@@ -46,7 +48,7 @@ SimダッシュボードのユーザーセッティングからAPIキーを生
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** レスポンス本文のレート制限はワークフロー実行に関するものです。このAPIエンドポイントを呼び出すためのレート制限はレスポンスヘッダー(`X-RateLimit-*`)にあります。
|
||||
**注意:** レート制限はトークンバケットアルゴリズムを使用しています。最近の割り当てを完全に使用していない場合、`remaining`は`requestsPerMinute`を超えて`maxBurst`まで達することができ、バーストトラフィックを許可します。レスポンスボディのレート制限はワークフロー実行のためのものです。このAPIエンドポイントを呼び出すためのレート制限はレスポンスヘッダー(`X-RateLimit-*`)にあります。
|
||||
|
||||
### ログの照会
|
||||
|
||||
@@ -110,13 +112,15 @@ SimダッシュボードのユーザーセッティングからAPIキーを生
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -190,13 +194,15 @@ SimダッシュボードのユーザーセッティングからAPIキーを生
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -482,17 +488,25 @@ Webhookシークレットを設定した場合、署名を検証してWebhookが
|
||||
|
||||
## レート制限
|
||||
|
||||
APIは公平な使用を確保するためにレート制限を実装しています:
|
||||
APIは**トークンバケットアルゴリズム**をレート制限に使用し、バーストトラフィックを許可しながら公平な使用を提供します:
|
||||
|
||||
- **無料プラン**: 1分あたり10リクエスト
|
||||
- **プロプラン**: 1分あたり30リクエスト
|
||||
- **チームプラン**: 1分あたり60リクエスト
|
||||
- **エンタープライズプラン**: カスタム制限
|
||||
| プラン | リクエスト/分 | バースト容量 |
|
||||
|------|-----------------|----------------|
|
||||
| 無料 | 10 | 20 |
|
||||
| プロ | 30 | 60 |
|
||||
| チーム | 60 | 120 |
|
||||
| エンタープライズ | 120 | 240 |
|
||||
|
||||
**仕組み:**
|
||||
- トークンは`requestsPerMinute`のレートで補充されます
|
||||
- アイドル状態のとき、最大`maxBurst`トークンまで蓄積できます
|
||||
- 各リクエストは1トークンを消費します
|
||||
- バースト容量によりトラフィックスパイクの処理が可能になります
|
||||
|
||||
レート制限情報はレスポンスヘッダーに含まれています:
|
||||
- `X-RateLimit-Limit`: ウィンドウあたりの最大リクエスト数
|
||||
- `X-RateLimit-Remaining`: 現在のウィンドウで残っているリクエスト数
|
||||
- `X-RateLimit-Reset`: ウィンドウがリセットされるISOタイムスタンプ
|
||||
- `X-RateLimit-Limit`:1分あたりのリクエスト数(補充レート)
|
||||
- `X-RateLimit-Remaining`:現在利用可能なトークン
|
||||
- `X-RateLimit-Reset`:トークンが次に補充されるISOタイムスタンプ
|
||||
|
||||
## 例:新しいログのポーリング
|
||||
|
||||
@@ -541,7 +555,7 @@ async function pollLogs() {
|
||||
setInterval(pollLogs, 30000);
|
||||
```
|
||||
|
||||
## 例:Webhookの処理
|
||||
## 例:ウェブフックの処理
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
|
||||
@@ -147,8 +147,20 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
{
|
||||
"success": true,
|
||||
"rateLimit": {
|
||||
"sync": { "isLimited": false, "limit": 10, "remaining": 10, "resetAt": "2025-09-08T22:51:55.999Z" },
|
||||
"async": { "isLimited": false, "limit": 50, "remaining": 50, "resetAt": "2025-09-08T22:51:56.155Z" },
|
||||
"sync": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 25,
|
||||
"maxBurst": 50,
|
||||
"remaining": 50,
|
||||
"resetAt": "2025-09-08T22:51:55.999Z"
|
||||
},
|
||||
"async": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 400,
|
||||
"resetAt": "2025-09-08T22:51:56.155Z"
|
||||
},
|
||||
"authType": "api"
|
||||
},
|
||||
"usage": {
|
||||
@@ -159,35 +171,40 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**レート制限フィールド:**
|
||||
- `requestsPerMinute`:持続的なレート制限(トークンはこの速度で補充されます)
|
||||
- `maxBurst`:蓄積できる最大トークン数(バースト容量)
|
||||
- `remaining`:現在利用可能なトークン(最大で`maxBurst`まで)
|
||||
|
||||
**レスポンスフィールド:**
|
||||
- `currentPeriodCost` は現在の請求期間の使用状況を反映します
|
||||
- `limit` は個別の制限(無料/プロ)または組織のプール制限(チーム/エンタープライズ)から導出されます
|
||||
- `plan` はユーザーに関連付けられた最優先のアクティブなプランです
|
||||
- `currentPeriodCost`は現在の請求期間の使用状況を反映します
|
||||
- `limit`は個別の制限(無料/プロ)または組織のプール制限(チーム/エンタープライズ)から派生します
|
||||
- `plan`はユーザーに関連付けられた最優先のアクティブなプランです
|
||||
|
||||
## プラン制限
|
||||
|
||||
異なるサブスクリプションプランには異なる使用制限があります:
|
||||
サブスクリプションプランによって使用制限が異なります:
|
||||
|
||||
| プラン | 月間使用制限 | レート制限(分あたり) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **無料** | $10 | 5 同期、10 非同期 |
|
||||
| **プロ** | $100 | 10 同期、50 非同期 |
|
||||
| **チーム** | $500(プール) | 50 同期、100 非同期 |
|
||||
| **無料** | $10 | 5同期、10非同期 |
|
||||
| **プロ** | $100 | 10同期、50非同期 |
|
||||
| **チーム** | $500(プール) | 50同期、100非同期 |
|
||||
| **エンタープライズ** | カスタム | カスタム |
|
||||
|
||||
## 課金モデル
|
||||
|
||||
Simは**基本サブスクリプション + 超過分**の課金モデルを使用しています:
|
||||
Simは**基本サブスクリプション+超過分**の課金モデルを使用しています:
|
||||
|
||||
### 仕組み
|
||||
|
||||
**プロプラン(月額$20):**
|
||||
- 月額サブスクリプションには$20分の使用量が含まれます
|
||||
- 使用量が$20未満 → 追加料金なし
|
||||
- 使用量が$20を超える → 月末に超過分を支払う
|
||||
- 使用量が$20を超える → 月末に超過分を支払い
|
||||
- 例:$35の使用量 = $20(サブスクリプション)+ $15(超過分)
|
||||
|
||||
**チームプラン(月額$40/シート):**
|
||||
**チームプラン(席あたり月額$40):**
|
||||
- チームメンバー全体でプールされた使用量
|
||||
- チーム全体の使用量から超過分を計算
|
||||
- 組織のオーナーが一括で請求を受ける
|
||||
@@ -209,10 +226,10 @@ Simは**基本サブスクリプション + 超過分**の課金モデルを使
|
||||
|
||||
## コスト管理のベストプラクティス
|
||||
|
||||
1. **定期的な監視**: 予想外の事態を避けるため、使用状況ダッシュボードを頻繁に確認する
|
||||
1. **定期的な監視**: 予期せぬ事態を避けるため、使用状況ダッシュボードを頻繁に確認する
|
||||
2. **予算の設定**: プランの制限を支出のガードレールとして使用する
|
||||
3. **ワークフローの最適化**: コストの高い実行を見直し、プロンプトやモデル選択を最適化する
|
||||
4. **適切なモデルの使用**: タスクの要件に合わせてモデルの複雑さを調整する
|
||||
4. **適切なモデルの使用**: タスクの要件にモデルの複雑さを合わせる
|
||||
5. **類似タスクのバッチ処理**: 可能な場合は複数のリクエストを組み合わせてオーバーヘッドを削減する
|
||||
|
||||
## 次のステップ
|
||||
|
||||
@@ -49,8 +49,8 @@ Kalshiの予測市場をワークフローに統合します。市場一覧、
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 市場データとメタデータ |
|
||||
| `markets` | array | 市場オブジェクトの配列 |
|
||||
| `paging` | object | さらに結果を取得するためのページネーションカーソル |
|
||||
|
||||
### `kalshi_get_market`
|
||||
|
||||
@@ -66,8 +66,7 @@ Kalshiの予測市場をワークフローに統合します。市場一覧、
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 市場データとメタデータ |
|
||||
| `market` | object | 詳細情報を含む市場オブジェクト |
|
||||
|
||||
### `kalshi_get_events`
|
||||
|
||||
@@ -87,8 +86,8 @@ Kalshiの予測市場をワークフローに統合します。市場一覧、
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | イベントデータとメタデータ |
|
||||
| `events` | array | イベントオブジェクトの配列 |
|
||||
| `paging` | object | さらに結果を取得するためのページネーションカーソル |
|
||||
|
||||
### `kalshi_get_event`
|
||||
|
||||
@@ -105,8 +104,7 @@ Kalshiの予測市場をワークフローに統合します。市場一覧、
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | イベントデータとメタデータ |
|
||||
| `event` | object | 詳細情報を含むイベントオブジェクト |
|
||||
|
||||
### `kalshi_get_balance`
|
||||
|
||||
@@ -123,8 +121,10 @@ Kalshiからアカウント残高とポートフォリオ価値を取得
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 残高データとメタデータ |
|
||||
| `balance` | number | セント単位のアカウント残高 |
|
||||
| `portfolioValue` | number | セント単位のポートフォリオ価値 |
|
||||
| `balanceDollars` | number | ドル単位のアカウント残高 |
|
||||
| `portfolioValueDollars` | number | ドル単位のポートフォリオ価値 |
|
||||
|
||||
### `kalshi_get_positions`
|
||||
|
||||
@@ -146,8 +146,8 @@ Kalshiからオープンポジションを取得
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | ポジションデータとメタデータ |
|
||||
| `positions` | array | ポジションオブジェクトの配列 |
|
||||
| `paging` | object | さらに結果を取得するためのページネーションカーソル |
|
||||
|
||||
### `kalshi_get_orders`
|
||||
|
||||
@@ -169,8 +169,8 @@ Kalshiからオープンポジションを取得
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 注文データとメタデータ |
|
||||
| `orders` | array | 注文オブジェクトの配列 |
|
||||
| `paging` | object | さらに結果を取得するためのページネーションカーソル |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
@@ -188,8 +188,7 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 注文データ |
|
||||
| `order` | object | 詳細情報を含む注文オブジェクト |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
@@ -205,8 +204,7 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 注文板データとメタデータ |
|
||||
| `orderbook` | object | yes/noの買い注文と売り注文を含むオーダーブック |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
@@ -223,8 +221,8 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 取引データとメタデータ |
|
||||
| `trades` | array | 取引オブジェクトの配列 |
|
||||
| `paging` | object | さらに結果を取得するためのページネーションカーソル |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
@@ -244,8 +242,7 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | ローソク足データとメタデータ |
|
||||
| `candlesticks` | array | OHLC(始値・高値・安値・終値)ローソク足データの配列 |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -268,8 +265,8 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 約定データとメタデータ |
|
||||
| `fills` | array | 約定/取引オブジェクトの配列 |
|
||||
| `paging` | object | さらに結果を取得するためのページネーションカーソル |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
@@ -285,8 +282,7 @@ IDを指定してKalshiから特定の注文の詳細を取得する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | シリーズデータとメタデータ |
|
||||
| `series` | object | 詳細を含むシリーズオブジェクト |
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
@@ -301,8 +297,7 @@ Kalshi取引所の現在のステータス(取引と取引所のアクティ
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 取引所ステータスデータとメタデータ |
|
||||
| `status` | object | trading_activeとexchange_activeフラグを含む取引所ステータス |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
@@ -336,8 +331,7 @@ Kalshi予測市場に新しい注文を作成する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 作成された注文データ |
|
||||
| `order` | object | 作成された注文オブジェクト |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
@@ -355,8 +349,8 @@ Kalshiで既存の注文をキャンセルする
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | キャンセルされた注文データ |
|
||||
| `order` | object | キャンセルされた注文オブジェクト |
|
||||
| `reducedBy` | number | キャンセルされた契約数 |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
@@ -384,8 +378,7 @@ Kalshiで既存の注文の価格または数量を変更する
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 変更された注文データ |
|
||||
| `order` | object | 変更された注文オブジェクト |
|
||||
|
||||
## 注意事項
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@ Polymarketから予測市場のリストをオプションのフィルタリン
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | いいえ | クローズ状態でフィルタリング(true/false)。アクティブなマーケットのみの場合はfalseを使用。 |
|
||||
| `closed` | string | いいえ | クローズ状態でフィルタリング(true/false)。アクティブな市場のみの場合はfalseを使用。 |
|
||||
| `order` | string | いいえ | ソートフィールド(例:volumeNum、liquidityNum、startDate、endDate、createdAt) |
|
||||
| `ascending` | string | いいえ | ソート方向(昇順の場合はtrue、降順の場合はfalse) |
|
||||
| `tagId` | string | いいえ | タグIDでフィルタリング |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(最大50) |
|
||||
| `offset` | string | いいえ | ページネーションオフセット(この数の結果をスキップ) |
|
||||
|
||||
#### 出力
|
||||
@@ -84,7 +84,7 @@ Polymarketからイベントのリストを取得し、オプションでフィ
|
||||
| `order` | string | いいえ | ソートフィールド(例:volume、liquidity、startDate、endDate、createdAt) |
|
||||
| `ascending` | string | いいえ | ソート方向(昇順の場合はtrue、降順の場合はfalse) |
|
||||
| `tagId` | string | いいえ | タグIDでフィルタリング |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(最大50) |
|
||||
| `offset` | string | いいえ | ページネーションオフセット(この数の結果をスキップ) |
|
||||
|
||||
#### 出力
|
||||
@@ -120,7 +120,7 @@ Polymarketからマーケットのフィルタリング用の利用可能なタ
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(最大50) |
|
||||
| `offset` | string | いいえ | ページネーションオフセット(この数の結果をスキップ) |
|
||||
|
||||
#### 出力
|
||||
@@ -139,15 +139,15 @@ Polymarketでマーケット、イベント、プロフィールを検索する
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | はい | 検索クエリ用語 |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
| `offset` | string | いいえ | ページネーションオフセット(この数の結果をスキップ) |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(最大50) |
|
||||
| `offset` | string | いいえ | ページネーションオフセット |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 検索結果とメタデータ |
|
||||
| `output` | object | 検索結果データとメタデータ |
|
||||
|
||||
### `polymarket_get_series`
|
||||
|
||||
@@ -157,7 +157,7 @@ Polymarketからシリーズ(関連するマーケットグループ)を取
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(最大50) |
|
||||
| `offset` | string | いいえ | ページネーションオフセット(この数の結果をスキップ) |
|
||||
|
||||
#### 出力
|
||||
@@ -199,7 +199,7 @@ PolymarketからIDで特定のシリーズ(関連する市場グループ)
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | オーダーブックデータとメタデータ |
|
||||
| `output` | object | 注文台帳データとメタデータ |
|
||||
|
||||
### `polymarket_get_price`
|
||||
|
||||
@@ -217,7 +217,7 @@ PolymarketからIDで特定のシリーズ(関連する市場グループ)
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 価格データとメタデータ |
|
||||
| `output` | object | 市場価格データとメタデータ |
|
||||
|
||||
### `polymarket_get_midpoint`
|
||||
|
||||
@@ -272,7 +272,7 @@ PolymarketからIDで特定のシリーズ(関連する市場グループ)
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 最終取引価格とメタデータ |
|
||||
| `output` | object | 最終取引価格データとメタデータ |
|
||||
|
||||
### `polymarket_get_spread`
|
||||
|
||||
@@ -306,7 +306,7 @@ PolymarketからIDで特定のシリーズ(関連する市場グループ)
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | ティックサイズとメタデータ |
|
||||
| `output` | object | 最小ティックサイズデータとメタデータ |
|
||||
|
||||
### `polymarket_get_positions`
|
||||
|
||||
@@ -336,15 +336,14 @@ Polymarketから取引履歴を取得する
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `user` | string | いいえ | 取引をフィルタリングするユーザーウォレットアドレス |
|
||||
| `market` | string | いいえ | 取引をフィルタリングするマーケットID |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(推奨:25-50) |
|
||||
| `limit` | string | いいえ | ページあたりの結果数(最大50) |
|
||||
| `offset` | string | いいえ | ページネーションオフセット(この数の結果をスキップ) |
|
||||
|
||||
#### 出力
|
||||
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `output` | object | 取引データとメタデータ |
|
||||
| `trades` | array | 取引オブジェクトの配列 |
|
||||
|
||||
## 注意事項
|
||||
|
||||
|
||||
@@ -342,19 +342,30 @@ SendGridからメールテンプレートを削除する
|
||||
| パラメータ | 型 | 説明 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功ステータス |
|
||||
| `messageId` | string | メールメッセージID(send_mail) |
|
||||
| `message` | string | ステータスまたは成功メッセージ |
|
||||
| `messageId` | string | メールメッセージID \(send_mail\) |
|
||||
| `to` | string | 受信者のメールアドレス \(send_mail\) |
|
||||
| `subject` | string | メールの件名 \(send_mail, create_template_version\) |
|
||||
| `id` | string | リソースID |
|
||||
| `jobId` | string | 非同期操作のジョブID |
|
||||
| `email` | string | メールアドレス |
|
||||
| `firstName` | string | 名 |
|
||||
| `lastName` | string | 姓 |
|
||||
| `jobId` | string | 非同期操作用のジョブID |
|
||||
| `email` | string | 連絡先のメールアドレス |
|
||||
| `firstName` | string | 連絡先の名 |
|
||||
| `lastName` | string | 連絡先の姓 |
|
||||
| `createdAt` | string | 作成タイムスタンプ |
|
||||
| `updatedAt` | string | 最終更新タイムスタンプ |
|
||||
| `listIds` | json | 連絡先が所属するリストIDの配列 |
|
||||
| `customFields` | json | カスタムフィールドの値 |
|
||||
| `contacts` | json | 連絡先の配列 |
|
||||
| `contactCount` | number | 連絡先の数 |
|
||||
| `lists` | json | リストの配列 |
|
||||
| `templates` | json | テンプレートの配列 |
|
||||
| `message` | string | ステータスまたは成功メッセージ |
|
||||
| `name` | string | リソース名 |
|
||||
| `generation` | string | テンプレート生成方法 |
|
||||
| `templates` | json | テンプレートの配列 |
|
||||
| `generation` | string | テンプレート世代 |
|
||||
| `versions` | json | テンプレートバージョンの配列 |
|
||||
| `templateId` | string | テンプレートID |
|
||||
| `active` | boolean | テンプレートバージョンがアクティブかどうか |
|
||||
| `htmlContent` | string | HTML内容 |
|
||||
| `plainContent` | string | プレーンテキスト内容 |
|
||||
|
||||
### `sendgrid_create_template_version`
|
||||
|
||||
|
||||
@@ -21,24 +21,28 @@ import { Image } from '@/components/ui/image'
|
||||
エディタ、APIへのデプロイ、またはチャットへのデプロイエクスペリエンスから始まるすべてのものにはスタートブロックを使用します。イベント駆動型ワークフローには他のトリガーも利用可能です:
|
||||
|
||||
<Cards>
|
||||
<Card title="スタート" href="/triggers/start">
|
||||
<Card title="Start" href="/triggers/start">
|
||||
エディタ実行、APIデプロイメント、チャットデプロイメントをサポートする統合エントリーポイント
|
||||
</Card>
|
||||
<Card title="ウェブフック" href="/triggers/webhook">
|
||||
外部ウェブフックペイロードを受信
|
||||
<Card title="Webhook" href="/triggers/webhook">
|
||||
外部のwebhookペイロードを受信
|
||||
</Card>
|
||||
<Card title="スケジュール" href="/triggers/schedule">
|
||||
<Card title="Schedule" href="/triggers/schedule">
|
||||
Cronまたは間隔ベースの実行
|
||||
</Card>
|
||||
<Card title="RSS Feed" href="/triggers/rss">
|
||||
新しいコンテンツのRSSとAtomフィードを監視
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## クイック比較
|
||||
|
||||
| トリガー | 開始条件 |
|
||||
|---------|-----------------|
|
||||
| **スタート** | エディタ実行、APIへのデプロイリクエスト、またはチャットメッセージ |
|
||||
| **スケジュール** | スケジュールブロックで管理されるタイマー |
|
||||
| **ウェブフック** | 受信HTTPリクエスト時 |
|
||||
| **Start** | エディタ実行、APIへのデプロイリクエスト、またはチャットメッセージ |
|
||||
| **Schedule** | スケジュールブロックで管理されるタイマー |
|
||||
| **Webhook** | 受信HTTPリクエスト時 |
|
||||
| **RSS Feed** | フィードに新しいアイテムが公開された時 |
|
||||
|
||||
> スタートブロックは常に `input`、`conversationId`、および `files` フィールドを公開します。追加の構造化データには入力フォーマットにカスタムフィールドを追加してください。
|
||||
|
||||
|
||||
49
apps/docs/content/docs/ja/triggers/rss.mdx
Normal file
49
apps/docs/content/docs/ja/triggers/rss.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: RSSフィード
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
RSSフィードブロックはRSSとAtomフィードを監視します - 新しいアイテムが公開されると、ワークフローが自動的にトリガーされます。
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/rss.png"
|
||||
alt="RSSフィードブロック"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## 設定
|
||||
|
||||
1. **RSSフィードブロックを追加** - RSSフィードブロックをドラッグしてワークフローを開始
|
||||
2. **フィードURLを入力** - 任意のRSSまたはAtomフィードのURLを貼り付け
|
||||
3. **デプロイ** - ワークフローをデプロイしてポーリングを有効化
|
||||
|
||||
デプロイ後、フィードは1分ごとに新しいアイテムをチェックします。
|
||||
|
||||
## 出力フィールド
|
||||
|
||||
| フィールド | 型 | 説明 |
|
||||
|-------|------|-------------|
|
||||
| `title` | string | アイテムのタイトル |
|
||||
| `link` | string | アイテムのリンク |
|
||||
| `pubDate` | string | 公開日 |
|
||||
| `item` | object | すべてのフィールドを含む生のアイテム |
|
||||
| `feed` | object | 生のフィードメタデータ |
|
||||
|
||||
マッピングされたフィールドに直接アクセスするか(`<rss.title>`)、任意のフィールドに生のオブジェクトを使用します(`<rss.item.author>`、`<rss.feed.language>`)。
|
||||
|
||||
## ユースケース
|
||||
|
||||
- **コンテンツ監視** - ブログ、ニュースサイト、または競合他社の更新を追跡
|
||||
- **ポッドキャスト自動化** - 新しいエピソードが公開されたときにワークフローをトリガー
|
||||
- **リリース追跡** - GitHubリリース、変更ログ、または製品アップデートを監視
|
||||
- **ソーシャルアグリゲーション** - RSSフィードを公開しているプラットフォームからコンテンツを収集
|
||||
|
||||
<Callout>
|
||||
RSSトリガーは、トリガーを保存した後に公開されたアイテムに対してのみ実行されます。既存のフィードアイテムは処理されません。
|
||||
</Callout>
|
||||
@@ -27,14 +27,16 @@ curl -H "x-api-key: YOUR_API_KEY" \
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60, // Max sync workflow executions per minute
|
||||
"remaining": 58, // Remaining sync workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 60, // Sustained rate limit per minute
|
||||
"maxBurst": 120, // Maximum burst capacity
|
||||
"remaining": 118, // Current tokens available (up to maxBurst)
|
||||
"resetAt": "..." // When tokens next refill
|
||||
},
|
||||
"async": {
|
||||
"limit": 60, // Max async workflow executions per minute
|
||||
"remaining": 59, // Remaining async workflow executions
|
||||
"resetAt": "..." // When the window resets
|
||||
"requestsPerMinute": 200, // Sustained rate limit per minute
|
||||
"maxBurst": 400, // Maximum burst capacity
|
||||
"remaining": 398, // Current tokens available
|
||||
"resetAt": "..." // When tokens next refill
|
||||
}
|
||||
},
|
||||
"usage": {
|
||||
@@ -46,7 +48,7 @@ curl -H "x-api-key: YOUR_API_KEY" \
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 响应正文中的速率限制是针对工作流执行的。调用此 API 端点的速率限制在响应标头中(`X-RateLimit-*`)。
|
||||
**注意:** 速率限制使用令牌桶算法。`remaining` 可以超过 `requestsPerMinute` 达到 `maxBurst`,当您最近未使用全部配额时,允许突发流量。响应正文中的速率限制适用于工作流执行。调用此 API 端点的速率限制在响应头中(`X-RateLimit-*`)。
|
||||
|
||||
### 查询日志
|
||||
|
||||
@@ -110,13 +112,15 @@ curl -H "x-api-key: YOUR_API_KEY" \
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -190,13 +194,15 @@ curl -H "x-api-key: YOUR_API_KEY" \
|
||||
"limits": {
|
||||
"workflowExecutionRateLimit": {
|
||||
"sync": {
|
||||
"limit": 60,
|
||||
"remaining": 58,
|
||||
"requestsPerMinute": 60,
|
||||
"maxBurst": 120,
|
||||
"remaining": 118,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
},
|
||||
"async": {
|
||||
"limit": 60,
|
||||
"remaining": 59,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 398,
|
||||
"resetAt": "2025-01-01T12:35:56.789Z"
|
||||
}
|
||||
},
|
||||
@@ -482,17 +488,25 @@ curl -H "x-api-key: YOUR_API_KEY" \
|
||||
|
||||
## 速率限制
|
||||
|
||||
API 实现了速率限制以确保公平使用:
|
||||
该 API 使用 **令牌桶算法** 进行速率限制,在提供公平使用的同时允许突发流量:
|
||||
|
||||
- **免费计划**:每分钟 10 次请求
|
||||
- **专业计划**:每分钟 30 次请求
|
||||
- **团队计划**:每分钟 60 次请求
|
||||
- **企业计划**:自定义限制
|
||||
| 计划 | 请求/分钟 | 突发容量 |
|
||||
|------|-----------|----------|
|
||||
| 免费 | 10 | 20 |
|
||||
| 专业版 | 30 | 60 |
|
||||
| 团队版 | 60 | 120 |
|
||||
| 企业版 | 120 | 240 |
|
||||
|
||||
速率限制信息包含在响应标头中:
|
||||
- `X-RateLimit-Limit`:每个窗口的最大请求数
|
||||
- `X-RateLimit-Remaining`:当前窗口中剩余的请求数
|
||||
- `X-RateLimit-Reset`:窗口重置时的 ISO 时间戳
|
||||
**工作原理:**
|
||||
- 令牌以 `requestsPerMinute` 的速率补充
|
||||
- 空闲时最多可累积 `maxBurst` 个令牌
|
||||
- 每个请求消耗 1 个令牌
|
||||
- 突发容量允许处理流量高峰
|
||||
|
||||
速率限制信息包含在响应头中:
|
||||
- `X-RateLimit-Limit`:每分钟请求数(补充速率)
|
||||
- `X-RateLimit-Remaining`:当前可用令牌数
|
||||
- `X-RateLimit-Reset`:令牌下次补充的 ISO 时间戳
|
||||
|
||||
## 示例:轮询新日志
|
||||
|
||||
@@ -541,7 +555,7 @@ async function pollLogs() {
|
||||
setInterval(pollLogs, 30000);
|
||||
```
|
||||
|
||||
## 示例:处理 Webhook
|
||||
## 示例:处理 Webhooks
|
||||
|
||||
```javascript
|
||||
import express from 'express';
|
||||
|
||||
@@ -147,8 +147,20 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
{
|
||||
"success": true,
|
||||
"rateLimit": {
|
||||
"sync": { "isLimited": false, "limit": 10, "remaining": 10, "resetAt": "2025-09-08T22:51:55.999Z" },
|
||||
"async": { "isLimited": false, "limit": 50, "remaining": 50, "resetAt": "2025-09-08T22:51:56.155Z" },
|
||||
"sync": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 25,
|
||||
"maxBurst": 50,
|
||||
"remaining": 50,
|
||||
"resetAt": "2025-09-08T22:51:55.999Z"
|
||||
},
|
||||
"async": {
|
||||
"isLimited": false,
|
||||
"requestsPerMinute": 200,
|
||||
"maxBurst": 400,
|
||||
"remaining": 400,
|
||||
"resetAt": "2025-09-08T22:51:56.155Z"
|
||||
},
|
||||
"authType": "api"
|
||||
},
|
||||
"usage": {
|
||||
@@ -159,6 +171,11 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
}
|
||||
```
|
||||
|
||||
**速率限制字段:**
|
||||
- `requestsPerMinute`:持续速率限制(令牌以此速率补充)
|
||||
- `maxBurst`:您可以累积的最大令牌数(突发容量)
|
||||
- `remaining`:当前可用令牌数(最多可达 `maxBurst`)
|
||||
|
||||
**响应字段:**
|
||||
- `currentPeriodCost` 反映当前计费周期的使用情况
|
||||
- `limit` 来源于个人限制(免费/专业)或组织池限制(团队/企业)
|
||||
@@ -170,9 +187,9 @@ curl -X GET -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" htt
|
||||
|
||||
| 计划 | 每月使用限制 | 速率限制(每分钟) |
|
||||
|------|-------------------|-------------------------|
|
||||
| **免费** | $10 | 5 同步, 10 异步 |
|
||||
| **专业** | $100 | 10 同步, 50 异步 |
|
||||
| **团队** | $500(共享) | 50 同步, 100 异步 |
|
||||
| **免费** | $10 | 5 同步,10 异步 |
|
||||
| **专业** | $100 | 10 同步,50 异步 |
|
||||
| **团队** | $500(共享) | 50 同步,100 异步 |
|
||||
| **企业** | 自定义 | 自定义 |
|
||||
|
||||
## 计费模式
|
||||
@@ -187,9 +204,9 @@ Sim 使用 **基础订阅 + 超额** 的计费模式:
|
||||
- 使用超过 $20 → 月底支付超额部分
|
||||
- 示例:$35 使用 = $20(订阅)+ $15(超额)
|
||||
|
||||
**团队计划($40/人/月):**
|
||||
- 团队成员共享使用额度
|
||||
- 超额部分根据团队总使用量计算
|
||||
**团队计划($40/每席位/月):**
|
||||
- 团队成员之间共享使用额度
|
||||
- 超额费用根据团队总使用量计算
|
||||
- 组织所有者收到一张账单
|
||||
|
||||
**企业计划:**
|
||||
@@ -198,20 +215,20 @@ Sim 使用 **基础订阅 + 超额** 的计费模式:
|
||||
|
||||
### 阈值计费
|
||||
|
||||
当未结算的超额费用达到 $50 时,Sim 会自动结算全部未结算金额。
|
||||
当未计费的超额费用达到 $50 时,Sim 会自动计费全额未计费金额。
|
||||
|
||||
**示例:**
|
||||
- 第 10 天:$70 超额 → 立即结算 $70
|
||||
- 第 15 天:额外使用 $35(总计 $105)→ 已结算,无需操作
|
||||
- 第 20 天:再使用 $50(总计 $155,未结算 $85)→ 立即结算 $85
|
||||
- 第 10 天:$70 超额 → 立即计费 $70
|
||||
- 第 15 天:额外使用 $35(总计 $105)→ 已计费,无需操作
|
||||
- 第 20 天:再使用 $50(总计 $155,未计费 $85)→ 立即计费 $85
|
||||
|
||||
这将把大量的超额费用分散到整个月,而不是在周期结束时收到一张大账单。
|
||||
这会将大量的超额费用分散到整个月,而不是在周期结束时收到一张大账单。
|
||||
|
||||
## 成本管理最佳实践
|
||||
|
||||
1. **定期监控**:经常检查您的使用仪表板,避免意外情况
|
||||
2. **设定预算**:使用计划限制作为支出控制的护栏
|
||||
3. **优化工作流程**:审查高成本的执行情况,优化提示或模型选择
|
||||
3. **优化工作流程**:审查高成本的执行操作,优化提示或模型选择
|
||||
4. **使用合适的模型**:根据任务需求匹配模型复杂度
|
||||
5. **批量处理相似任务**:尽可能合并多个请求以减少开销
|
||||
|
||||
|
||||
@@ -49,8 +49,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 市场数据和元数据 |
|
||||
| `markets` | array | 市场对象的数组 |
|
||||
| `paging` | object | 用于获取更多结果的分页游标 |
|
||||
|
||||
### `kalshi_get_market`
|
||||
|
||||
@@ -66,8 +66,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 市场数据和元数据 |
|
||||
| `market` | object | 包含详细信息的市场对象 |
|
||||
|
||||
### `kalshi_get_events`
|
||||
|
||||
@@ -87,8 +86,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 事件数据和元数据 |
|
||||
| `events` | array | 事件对象的数组 |
|
||||
| `paging` | object | 用于获取更多结果的分页游标 |
|
||||
|
||||
### `kalshi_get_event`
|
||||
|
||||
@@ -105,8 +104,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 事件数据和元数据 |
|
||||
| `event` | object | 包含详细信息的事件对象 |
|
||||
|
||||
### `kalshi_get_balance`
|
||||
|
||||
@@ -123,8 +121,10 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 余额数据和元数据 |
|
||||
| `balance` | number | 账户余额(以分为单位) |
|
||||
| `portfolioValue` | number | 投资组合价值(以分为单位) |
|
||||
| `balanceDollars` | number | 账户余额(以美元为单位) |
|
||||
| `portfolioValueDollars` | number | 投资组合价值(以美元为单位) |
|
||||
|
||||
### `kalshi_get_positions`
|
||||
|
||||
@@ -146,8 +146,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 位置数据和元数据 |
|
||||
| `positions` | array | 持仓对象的数组 |
|
||||
| `paging` | object | 用于获取更多结果的分页游标 |
|
||||
|
||||
### `kalshi_get_orders`
|
||||
|
||||
@@ -169,8 +169,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 订单数据和元数据 |
|
||||
| `orders` | array | 订单对象的数组 |
|
||||
| `paging` | object | 用于获取更多结果的分页游标 |
|
||||
|
||||
### `kalshi_get_order`
|
||||
|
||||
@@ -188,8 +188,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 订单数据 |
|
||||
| `order` | object | 包含详细信息的订单对象 |
|
||||
|
||||
### `kalshi_get_orderbook`
|
||||
|
||||
@@ -205,8 +204,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 订单簿数据和元数据 |
|
||||
| `orderbook` | object | 包含买入/卖出报价的订单簿 |
|
||||
|
||||
### `kalshi_get_trades`
|
||||
|
||||
@@ -223,8 +221,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 交易数据和元数据 |
|
||||
| `trades` | array | 交易对象的数组 |
|
||||
| `paging` | object | 用于获取更多结果的分页游标 |
|
||||
|
||||
### `kalshi_get_candlesticks`
|
||||
|
||||
@@ -244,8 +242,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 蜡烛图数据和元数据 |
|
||||
| `candlesticks` | array | OHLC 蜡烛图数据的数组 |
|
||||
|
||||
### `kalshi_get_fills`
|
||||
|
||||
@@ -268,8 +265,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 填充数据和元数据 |
|
||||
| `fills` | array | 成交/交易对象的数组 |
|
||||
| `paging` | object | 用于获取更多结果的分页游标 |
|
||||
|
||||
### `kalshi_get_series_by_ticker`
|
||||
|
||||
@@ -285,8 +282,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 系列数据和元数据 |
|
||||
| `series` | object | 包含详细信息的系列对象 |
|
||||
|
||||
### `kalshi_get_exchange_status`
|
||||
|
||||
@@ -301,8 +297,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 交易所状态数据和元数据 |
|
||||
| `status` | object | 包含 trading_active 和 exchange_active 标志的交易所状态 |
|
||||
|
||||
### `kalshi_create_order`
|
||||
|
||||
@@ -336,8 +331,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 创建的订单数据 |
|
||||
| `order` | object | 创建的订单对象 |
|
||||
|
||||
### `kalshi_cancel_order`
|
||||
|
||||
@@ -355,8 +349,8 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 已取消的订单数据 |
|
||||
| `order` | object | 已取消的订单对象 |
|
||||
| `reducedBy` | number | 已取消的合约数量 |
|
||||
|
||||
### `kalshi_amend_order`
|
||||
|
||||
@@ -384,8 +378,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 修改后的订单数据 |
|
||||
| `order` | object | 修改后的订单对象 |
|
||||
|
||||
## 注意
|
||||
|
||||
|
||||
@@ -41,18 +41,17 @@ Polymarket 集成的主要功能包括:
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | 否 | 按关闭状态筛选 \(true/false\)。使用 false 仅显示活跃市场。 |
|
||||
| `order` | string | 否 | 排序字段 \(例如:volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | 否 | 排序方向 \(true 表示升序,false 表示降序\) |
|
||||
| `order` | string | 否 | 排序字段 \(例如,volumeNum, liquidityNum, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | 否 | 排序方向 \(true 为升序,false 为降序\) |
|
||||
| `tagId` | string | 否 | 按标签 ID 筛选 |
|
||||
| `limit` | string | 否 | 每页结果数量 \(推荐:25-50\) |
|
||||
| `limit` | string | 否 | 每页结果数量 \(最大 50\) |
|
||||
| `offset` | string | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | 布尔值 | 操作成功状态 |
|
||||
| `output` | 对象 | 市场数据和元数据 |
|
||||
| `markets` | array | 市场对象数组 |
|
||||
|
||||
### `polymarket_get_market`
|
||||
|
||||
@@ -69,8 +68,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 市场数据和元数据 |
|
||||
| `market` | object | 包含详细信息的市场对象 |
|
||||
|
||||
### `polymarket_get_events`
|
||||
|
||||
@@ -81,18 +79,17 @@ Polymarket 集成的主要功能包括:
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `closed` | string | 否 | 按关闭状态筛选 \(true/false\)。使用 false 仅显示活跃事件。 |
|
||||
| `order` | string | 否 | 排序字段 \(例如:volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | 否 | 排序方向 \(true 表示升序,false 表示降序\) |
|
||||
| `order` | string | 否 | 排序字段 \(例如,volume, liquidity, startDate, endDate, createdAt\) |
|
||||
| `ascending` | string | 否 | 排序方向 \(true 为升序,false 为降序\) |
|
||||
| `tagId` | string | 否 | 按标签 ID 筛选 |
|
||||
| `limit` | string | 否 | 每页结果数量 \(推荐:25-50\) |
|
||||
| `limit` | string | 否 | 每页结果数量 \(最大 50\) |
|
||||
| `offset` | string | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 事件数据和元数据 |
|
||||
| `events` | 数组 | 事件对象的数组 |
|
||||
|
||||
### `polymarket_get_event`
|
||||
|
||||
@@ -109,8 +106,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 事件数据和元数据 |
|
||||
| `event` | 对象 | 包含详细信息的事件对象 |
|
||||
|
||||
### `polymarket_get_tags`
|
||||
|
||||
@@ -120,15 +116,14 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | 否 | 每页结果数量 \(推荐:25-50\) |
|
||||
| `offset` | string | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
| `limit` | 字符串 | 否 | 每页结果数量 \(最多 50\) |
|
||||
| `offset` | 字符串 | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 标签数据和元数据 |
|
||||
| `tags` | 数组 | 包含 id、标签和 slug 的标签对象数组 |
|
||||
|
||||
### `polymarket_search`
|
||||
|
||||
@@ -138,16 +133,15 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `query` | string | 是 | 搜索查询词 |
|
||||
| `limit` | string | 否 | 每页结果数量 \(推荐:25-50\) |
|
||||
| `offset` | string | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
| `query` | 字符串 | 是 | 搜索查询词 |
|
||||
| `limit` | 字符串 | 否 | 每页结果数量 \(最多 50\) |
|
||||
| `offset` | 字符串 | 否 | 分页偏移量 |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 搜索结果和元数据 |
|
||||
| `results` | 对象 | 包含市场、事件和个人资料数组的搜索结果 |
|
||||
|
||||
### `polymarket_get_series`
|
||||
|
||||
@@ -157,15 +151,14 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `limit` | string | 否 | 每页结果数量 \(推荐:25-50\) |
|
||||
| `offset` | string | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
| `limit` | 字符串 | 否 | 每页结果数量 \(最多 50\) |
|
||||
| `offset` | 字符串 | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 系列数据和元数据 |
|
||||
| `series` | 数组 | 系列对象的数组 |
|
||||
|
||||
### `polymarket_get_series_by_id`
|
||||
|
||||
@@ -181,8 +174,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 系列数据和元数据 |
|
||||
| `series` | 对象 | 包含详细信息的系列对象 |
|
||||
|
||||
### `polymarket_get_orderbook`
|
||||
|
||||
@@ -198,8 +190,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 订单簿数据和元数据 |
|
||||
| `orderbook` | 对象 | 包含买入和卖出数组的订单簿 |
|
||||
|
||||
### `polymarket_get_price`
|
||||
|
||||
@@ -216,8 +207,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 价格数据和元数据 |
|
||||
| `price` | 字符串 | 市场价格 |
|
||||
|
||||
### `polymarket_get_midpoint`
|
||||
|
||||
@@ -233,8 +223,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 中间价格数据和元数据 |
|
||||
| `midpoint` | 字符串 | 中点价格 |
|
||||
|
||||
### `polymarket_get_price_history`
|
||||
|
||||
@@ -254,8 +243,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 价格历史数据和元数据 |
|
||||
| `history` | 数组 | 包含时间戳 \(t\) 和价格 \(p\) 的价格历史条目数组 |
|
||||
|
||||
### `polymarket_get_last_trade_price`
|
||||
|
||||
@@ -271,8 +259,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 最新交易价格和元数据 |
|
||||
| `price` | 字符串 | 最新交易价格 |
|
||||
|
||||
### `polymarket_get_spread`
|
||||
|
||||
@@ -288,8 +275,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 价差数据和元数据 |
|
||||
| `spread` | 对象 | 包含买价和卖价的买卖价差 |
|
||||
|
||||
### `polymarket_get_tick_size`
|
||||
|
||||
@@ -305,8 +291,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 跳动单位和元数据 |
|
||||
| `tickSize` | 字符串 | 最小价格变动单位 |
|
||||
|
||||
### `polymarket_get_positions`
|
||||
|
||||
@@ -323,8 +308,7 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 持仓数据和元数据 |
|
||||
| `positions` | 数组 | 持仓对象的数组 |
|
||||
|
||||
### `polymarket_get_trades`
|
||||
|
||||
@@ -334,17 +318,16 @@ Polymarket 集成的主要功能包括:
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `user` | string | 否 | 用户钱包地址,用于筛选交易 |
|
||||
| `market` | string | 否 | 市场 ID,用于筛选交易 |
|
||||
| `limit` | string | 否 | 每页结果数量(推荐:25-50) |
|
||||
| `offset` | string | 否 | 分页偏移量(跳过此数量的结果) |
|
||||
| `user` | 字符串 | 否 | 用于筛选交易的用户钱包地址 |
|
||||
| `market` | 字符串 | 否 | 用于筛选交易的市场 ID |
|
||||
| `limit` | 字符串 | 否 | 每页结果数量 \(最多 50\) |
|
||||
| `offset` | 字符串 | 否 | 分页偏移量 \(跳过此数量的结果\) |
|
||||
|
||||
#### 输出
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `output` | object | 交易数据和元数据 |
|
||||
| `trades` | 数组 | 交易对象的数组 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
|
||||
@@ -342,19 +342,30 @@ SendGrid 的主要功能包括:
|
||||
| 参数 | 类型 | 描述 |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | 操作成功状态 |
|
||||
| `messageId` | string | 电子邮件消息 ID \(send_mail\) |
|
||||
| `message` | string | 状态或成功消息 |
|
||||
| `messageId` | string | 邮件消息 ID \(send_mail\) |
|
||||
| `to` | string | 收件人邮箱地址 \(send_mail\) |
|
||||
| `subject` | string | 邮件主题 \(send_mail, create_template_version\) |
|
||||
| `id` | string | 资源 ID |
|
||||
| `jobId` | string | 异步操作的作业 ID |
|
||||
| `email` | string | 电子邮件地址 |
|
||||
| `firstName` | string | 名字 |
|
||||
| `lastName` | string | 姓氏 |
|
||||
| `email` | string | 联系人邮箱地址 |
|
||||
| `firstName` | string | 联系人名字 |
|
||||
| `lastName` | string | 联系人姓氏 |
|
||||
| `createdAt` | string | 创建时间戳 |
|
||||
| `updatedAt` | string | 最后更新时间戳 |
|
||||
| `listIds` | json | 联系人所属列表 ID 的数组 |
|
||||
| `customFields` | json | 自定义字段值 |
|
||||
| `contacts` | json | 联系人数组 |
|
||||
| `contactCount` | number | 联系人数量 |
|
||||
| `contactCount` | number | 联系人数 |
|
||||
| `lists` | json | 列表数组 |
|
||||
| `templates` | json | 模板数组 |
|
||||
| `message` | string | 状态或成功消息 |
|
||||
| `name` | string | 资源名称 |
|
||||
| `generation` | string | 模板生成方式 |
|
||||
| `templates` | json | 模板数组 |
|
||||
| `generation` | string | 模板生成 |
|
||||
| `versions` | json | 模板版本数组 |
|
||||
| `templateId` | string | 模板 ID |
|
||||
| `active` | boolean | 模板版本是否激活 |
|
||||
| `htmlContent` | string | HTML 内容 |
|
||||
| `plainContent` | string | 纯文本内容 |
|
||||
|
||||
### `sendgrid_create_template_version`
|
||||
|
||||
|
||||
@@ -21,24 +21,28 @@ import { Image } from '@/components/ui/image'
|
||||
使用 Start 块处理从编辑器、部署到 API 或部署到聊天的所有操作。其他触发器可用于事件驱动的工作流:
|
||||
|
||||
<Cards>
|
||||
<Card title="Start" href="/triggers/start">
|
||||
<Card title="开始" href="/triggers/start">
|
||||
支持编辑器运行、API 部署和聊天部署的统一入口点
|
||||
</Card>
|
||||
<Card title="Webhook" href="/triggers/webhook">
|
||||
接收外部 webhook 负载
|
||||
</Card>
|
||||
<Card title="Schedule" href="/triggers/schedule">
|
||||
<Card title="计划" href="/triggers/schedule">
|
||||
基于 Cron 或间隔的执行
|
||||
</Card>
|
||||
<Card title="RSS 源" href="/triggers/rss">
|
||||
监控 RSS 和 Atom 源的新内容
|
||||
</Card>
|
||||
</Cards>
|
||||
|
||||
## 快速对比
|
||||
|
||||
| 触发器 | 启动条件 |
|
||||
|---------|-----------------|
|
||||
| **Start** | 编辑器运行、部署到 API 请求或聊天消息 |
|
||||
| **Schedule** | 在 Schedule 块中管理的计时器 |
|
||||
| **Webhook** | 收到入站 HTTP 请求 |
|
||||
| **开始** | 编辑器运行、部署到 API 请求或聊天消息 |
|
||||
| **计划** | 在计划块中管理的计时器 |
|
||||
| **Webhook** | 收到入站 HTTP 请求时 |
|
||||
| **RSS 源** | 源中发布了新项目 |
|
||||
|
||||
> Start 块始终公开 `input`、`conversationId` 和 `files` 字段。通过向输入格式添加自定义字段来增加结构化数据。
|
||||
|
||||
|
||||
49
apps/docs/content/docs/zh/triggers/rss.mdx
Normal file
49
apps/docs/content/docs/zh/triggers/rss.mdx
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: RSS 订阅源
|
||||
---
|
||||
|
||||
import { Callout } from 'fumadocs-ui/components/callout'
|
||||
import { Image } from '@/components/ui/image'
|
||||
|
||||
RSS 订阅源模块监控 RSS 和 Atom 订阅源——当有新内容发布时,您的工作流会自动触发。
|
||||
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
src="/static/blocks/rss.png"
|
||||
alt="RSS 订阅源模块"
|
||||
width={500}
|
||||
height={400}
|
||||
className="my-6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
## 配置
|
||||
|
||||
1. **添加 RSS 订阅源模块** - 拖动 RSS 订阅源模块以开始您的工作流
|
||||
2. **输入订阅源 URL** - 粘贴任意 RSS 或 Atom 订阅源的 URL
|
||||
3. **部署** - 部署您的工作流以激活轮询
|
||||
|
||||
部署后,订阅源每分钟检查一次是否有新内容。
|
||||
|
||||
## 输出字段
|
||||
|
||||
| 字段 | 类型 | 描述 |
|
||||
|-------|------|-------------|
|
||||
| `title` | string | 内容标题 |
|
||||
| `link` | string | 内容链接 |
|
||||
| `pubDate` | string | 发布日期 |
|
||||
| `item` | object | 包含所有字段的原始内容 |
|
||||
| `feed` | object | 原始订阅源元数据 |
|
||||
|
||||
可以直接访问映射字段 (`<rss.title>`),或者使用原始对象访问任意字段 (`<rss.item.author>`, `<rss.feed.language>`)。
|
||||
|
||||
## 使用场景
|
||||
|
||||
- **内容监控** - 跟踪博客、新闻网站或竞争对手的更新
|
||||
- **播客自动化** - 当新剧集发布时触发工作流
|
||||
- **版本跟踪** - 监控 GitHub 发布、更新日志或产品更新
|
||||
- **社交聚合** - 收集支持 RSS 订阅源的平台内容
|
||||
|
||||
<Callout>
|
||||
RSS 触发器仅对您保存触发器后发布的内容生效。现有的订阅源内容不会被处理。
|
||||
</Callout>
|
||||
@@ -4578,25 +4578,26 @@ checksums:
|
||||
content/32: 7b5e2207a0d93fd434b92f2f290a8dd5
|
||||
content/33: f950b8f58af1973a3e00393d860bce02
|
||||
content/34: d5ff07fec9455183e1d93f7ddf1dab1b
|
||||
content/35: 405286ad5695582bf752b48aeeef3928
|
||||
content/36: 7bb928aba33a4013ad5f08487da5bbf9
|
||||
content/37: dbbf313837f13ddfa4a8843d71cb9cc4
|
||||
content/38: cf10560ae6defb8ee5da344fc6509f6e
|
||||
content/39: c5dc6e5de6e45b17ee1f5eb567a18e2f
|
||||
content/40: 332dab0588fb35dabb64b674ba6120eb
|
||||
content/41: 714b3f99b0a8686bbb3434deb1f682b3
|
||||
content/42: ba18ac99184b17d7e49bd1abdc814437
|
||||
content/43: bed2b629274d55c38bd637e6a28dbc4a
|
||||
content/44: 71487ae6f6fb1034d1787456de442e6d
|
||||
content/45: 137d9874cf5ec8d09bd447f224cc7a7c
|
||||
content/46: 6b5b4c3b2f98b8fc7dd908fef2605ce8
|
||||
content/47: 3af6812662546ce647a55939241fd88e
|
||||
content/48: 6a4d7f0ccb8c28303251d1ef7b3dcca7
|
||||
content/49: 5dce779f77cc2b0abf12802a833df499
|
||||
content/50: aa47ff01b631252f024eaaae0c773e42
|
||||
content/51: 1266d1c7582bb617cdef56857be34f30
|
||||
content/52: c2cef2688104adaf6641092f43d4969a
|
||||
content/53: 089fc64b4589b2eaa371de7e04c4aed9
|
||||
content/35: 5d2d85e082d9fdd3859fb5c788d5f9a3
|
||||
content/36: 23a7de9c5adb6e07c28c23a9d4e03dc2
|
||||
content/37: 7bb928aba33a4013ad5f08487da5bbf9
|
||||
content/38: dbbf313837f13ddfa4a8843d71cb9cc4
|
||||
content/39: cf10560ae6defb8ee5da344fc6509f6e
|
||||
content/40: c5dc6e5de6e45b17ee1f5eb567a18e2f
|
||||
content/41: 332dab0588fb35dabb64b674ba6120eb
|
||||
content/42: 714b3f99b0a8686bbb3434deb1f682b3
|
||||
content/43: ba18ac99184b17d7e49bd1abdc814437
|
||||
content/44: bed2b629274d55c38bd637e6a28dbc4a
|
||||
content/45: 71487ae6f6fb1034d1787456de442e6d
|
||||
content/46: 137d9874cf5ec8d09bd447f224cc7a7c
|
||||
content/47: 6b5b4c3b2f98b8fc7dd908fef2605ce8
|
||||
content/48: 3af6812662546ce647a55939241fd88e
|
||||
content/49: 6a4d7f0ccb8c28303251d1ef7b3dcca7
|
||||
content/50: 5dce779f77cc2b0abf12802a833df499
|
||||
content/51: aa47ff01b631252f024eaaae0c773e42
|
||||
content/52: 1266d1c7582bb617cdef56857be34f30
|
||||
content/53: c2cef2688104adaf6641092f43d4969a
|
||||
content/54: 089fc64b4589b2eaa371de7e04c4aed9
|
||||
722959335ba76c9d0097860e2ad5a952:
|
||||
meta/title: 1f5b53b9904ec41d49c1e726e3d56b40
|
||||
content/0: c2b41859d63a751682f0d9aec488e581
|
||||
@@ -4638,8 +4639,8 @@ checksums:
|
||||
content/5: a29eaf87ca102db3f76a230f3651e767
|
||||
content/6: de14c379db04a8dec394bce98e250254
|
||||
content/7: 342e6c2c1e62d359bb47afe2be506c9a
|
||||
content/8: fda5abcc2ed8a952f1d71fdf5e9df3f0
|
||||
content/9: 25390578c4e18022ac486438a39695f5
|
||||
content/8: 87ae5ed07aa69cb7816ffbadb4abf3f7
|
||||
content/9: 5f75d4e4c56a5e56fd4b51aaaadc12ca
|
||||
content/10: 841c6cfb0d4eb481cdb81dd170220ea0
|
||||
content/11: 5fedb9fbc9445df0bf90a6d47710b1a6
|
||||
content/12: c0ac34bbe27e2ff5272773e755641b5b
|
||||
@@ -4648,14 +4649,14 @@ checksums:
|
||||
content/15: 7f611a62b68373a068eb5dee13e53416
|
||||
content/16: b05fe494158f2a347ced83b8d91a1445
|
||||
content/17: 7c3a4621fd5a0e235fa98d2d3b76c12f
|
||||
content/18: a192e0ca07a6cad306fa96b9942b7db4
|
||||
content/18: 8afd31ae50ad8c39689c25d9ffd530bc
|
||||
content/19: 3304a33dfb626c6e2267c062e8956a9d
|
||||
content/20: 6f09faac9d13188e039a75ddccebc214
|
||||
content/21: a450f2a3dc78405c47859dd35084e385
|
||||
content/22: c0ac34bbe27e2ff5272773e755641b5b
|
||||
content/23: 747141da074489515b6173c29d273403
|
||||
content/24: 620d49f6fa198176bf6e59685e246e2c
|
||||
content/25: fbea753ec4fb70bb0f2b61005fd3db18
|
||||
content/25: b83b4c7d69d3cee9bc6873fe3da4bdc3
|
||||
content/26: 3304a33dfb626c6e2267c062e8956a9d
|
||||
content/27: 59c0fe74307c15d310aa16b09e835ddd
|
||||
content/28: 53b824557984ddecb9b8c3e695967bc9
|
||||
@@ -4708,13 +4709,14 @@ checksums:
|
||||
content/75: c13873edfa750ce6caa155ef2f3f1883
|
||||
content/76: 0bc07e36a42f7e25d5426b6a9deaac02
|
||||
content/77: 017c829dd54fde20fa9fac7712342236
|
||||
content/78: ceba97287cca68b1291142a38175d6d8
|
||||
content/79: 02072ea1e956490139dbe8bbb0bc3e16
|
||||
content/80: 44871834b9cb423a9978843c08623979
|
||||
content/81: 0b22ed8a7e64005c666505c48e09f715
|
||||
content/82: 494dcadaea5e62eddd599700511ecee5
|
||||
content/83: 8332b16a0bf7a4c862f5104e9ffeb98d
|
||||
content/84: 90e2f984a874a8f954ddfd127ec8178a
|
||||
content/78: 6a8e85d2a7c36c724f752b93471122ea
|
||||
content/79: b8e19306e2311b611318f8f89bb67971
|
||||
content/80: 9ddcf8728a2fb2f293894bef4ddc98b4
|
||||
content/81: 836487497de0b58afd0abff2ae5f044a
|
||||
content/82: 0b22ed8a7e64005c666505c48e09f715
|
||||
content/83: 494dcadaea5e62eddd599700511ecee5
|
||||
content/84: 8332b16a0bf7a4c862f5104e9ffeb98d
|
||||
content/85: 90e2f984a874a8f954ddfd127ec8178a
|
||||
0e322683b6d10e9fa8c9a17ff15a5fb1:
|
||||
meta/title: a912b3c7fb996fefccb182cf5c4a3fbc
|
||||
content/0: e1f8d4b13687e7d73b5b5fbb4cb6142d
|
||||
@@ -5760,9 +5762,9 @@ checksums:
|
||||
content/1: e71056df0f7b2eb3b2f271f21d0052cc
|
||||
content/2: da2b445db16c149f56558a4ea876a5f0
|
||||
content/3: cec18f48b2cd7974eb556880e6604f7f
|
||||
content/4: c187ae3362455acfe43282399f0d163a
|
||||
content/4: b200402d6a01ab565fd56d113c530ef6
|
||||
content/5: 4c3a5708af82c1ee42a12d14fd34e950
|
||||
content/6: 12a43b499c1e8bb06b050964053ebde3
|
||||
content/6: 64fbd5b16f4cff18ba976492a275c05e
|
||||
content/7: a28151eeb5ba3518b33809055b04f0f6
|
||||
content/8: cffe5b901d78ebf2000d07dc7579533e
|
||||
content/9: 73486253d24eeff7ac44dfd0c8868d87
|
||||
@@ -47364,7 +47366,7 @@ checksums:
|
||||
content/98: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/99: 23aaf026b4d450b5bd6047e3acfe9a71
|
||||
content/100: bcadfc362b69078beee0088e5936c98b
|
||||
content/101: 7dec1de52492e6d0575c136baa1a8f92
|
||||
content/101: 0c881f3d4490d2505a6f2258481370cb
|
||||
content/102: 22512b23811c21236e3ec39743e30267
|
||||
content/103: e70757a620b6d53b8232652c6f224100
|
||||
content/104: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
@@ -48104,105 +48106,105 @@ checksums:
|
||||
content/10: bb5d0521074c58da3f6b997faef887ae
|
||||
content/11: 147f49b8a7a56cb60c881117480a71fb
|
||||
content/12: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/13: 64096864823f19e5e62d794315454652
|
||||
content/13: 8630b3b5b2372b19370c72091300ad36
|
||||
content/14: bcadfc362b69078beee0088e5936c98b
|
||||
content/15: 7311861512fa50f2c937d080692151e8
|
||||
content/15: a85d1bc2e2dcee136f814dc84e7596d9
|
||||
content/16: fbb677a4902291738c3a1f8b9303fd4a
|
||||
content/17: 840eb13be6ecb8f1cf652ae9ea3f3dc8
|
||||
content/18: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/19: 3dca9d793f1cf0619733a4aef965f385
|
||||
content/20: bcadfc362b69078beee0088e5936c98b
|
||||
content/21: 4634d3f690b2b1977d4555b2d4eee739
|
||||
content/21: d25c6e706996660030e1ce66bcdb9e7e
|
||||
content/22: 25e7fef7953155abe4219e03058c2f94
|
||||
content/23: 1ba7d90c16ff260798c13836a74aad6a
|
||||
content/24: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/25: 0f201f6957d27eaf9d1c4b4eab6ec8f7
|
||||
content/25: d08681763f016af95e9608b1bab4c437
|
||||
content/26: bcadfc362b69078beee0088e5936c98b
|
||||
content/27: a88bd6e0708b4c3017be42eabc480947
|
||||
content/27: 97d28b81355d5c4baf0775ffde238873
|
||||
content/28: 528f164231d5bf39fcd8849734efebde
|
||||
content/29: 6f779ec439c0e4b8e42370a6d3db4a5e
|
||||
content/30: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/31: dee5cd45e0dcd6d19acf9081d0c8c805
|
||||
content/32: bcadfc362b69078beee0088e5936c98b
|
||||
content/33: b6656b4d0cb132b838d73e758850e3f9
|
||||
content/33: fdba9cfce336484ab03fc2ac32bffea9
|
||||
content/34: 78c6c8168c1295ea5aaab803874a6795
|
||||
content/35: 566a0073bd48010fdaeac9388c100042
|
||||
content/36: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/37: 13677e745575edf438b603aa13bac889
|
||||
content/37: 7ea05e4c143f6c0d96d4f99b783a56f4
|
||||
content/38: bcadfc362b69078beee0088e5936c98b
|
||||
content/39: edb564cb480f1375dbc22c1553b1abe4
|
||||
content/39: 68e2f713a365644dd8b8cffb8e16413c
|
||||
content/40: 887cc3ce330dc18a3cf13692f28a4ade
|
||||
content/41: c9b49024bd518a859dc76fb30eb46f94
|
||||
content/42: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/43: 6c107c32338f118f6849c576107480f4
|
||||
content/43: aed6b4d347aef9692e345670dfa3e683
|
||||
content/44: bcadfc362b69078beee0088e5936c98b
|
||||
content/45: fb073b94e2c2ca29212d093d5b103b87
|
||||
content/45: 5862b3ba004ec511ea7f1dd972145348
|
||||
content/46: 5f54ceacaf53a1db8bda5399ae205831
|
||||
content/47: 3401fea610db18780a989ea02d8841aa
|
||||
content/48: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/49: 13677e745575edf438b603aa13bac889
|
||||
content/49: 7ea05e4c143f6c0d96d4f99b783a56f4
|
||||
content/50: bcadfc362b69078beee0088e5936c98b
|
||||
content/51: 08aaa5e1b8c8e8c146f68228e6d53792
|
||||
content/51: 30f09d8ee99a42c072c44db4703a91a4
|
||||
content/52: 19a1b9442970caf8758d0ffc7f179574
|
||||
content/53: 10a83dde0830cd58d4cb19dd8a0c3649
|
||||
content/54: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/55: c0d2832eacdb9bd8c8d9156019956cf7
|
||||
content/56: bcadfc362b69078beee0088e5936c98b
|
||||
content/57: 08aaa5e1b8c8e8c146f68228e6d53792
|
||||
content/57: d05aa3c271674d80fd34fe5d7c0378fd
|
||||
content/58: 17afc458eef6484867c139fd212d90d9
|
||||
content/59: da1f2733213d33e0509dc557bc6c80a8
|
||||
content/60: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/61: 42c3b4d062ae50f6ebaf9a7460a6fc5e
|
||||
content/62: bcadfc362b69078beee0088e5936c98b
|
||||
content/63: d6ec19129a153bc0667e76edb9c2953d
|
||||
content/63: 35bbaf81608917e5e0f8e1822fd2d3f8
|
||||
content/64: cc46a76f9ce04f5557e34fca2d6562c1
|
||||
content/65: 7c3b126537de352cabfc495892892c1c
|
||||
content/66: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/67: 954008ba49a8401ae8a22f7092e6ca23
|
||||
content/68: bcadfc362b69078beee0088e5936c98b
|
||||
content/69: b3764199e9c88e86cd0fe6935e539a25
|
||||
content/69: e343008f5f0c6e61e389fd7ba47bd183
|
||||
content/70: c635592e55ceed2da66ad0aa9c5f0ee5
|
||||
content/71: 90bf1835ccec7f5ee9b7d5ffb3965654
|
||||
content/72: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/73: 42c3b4d062ae50f6ebaf9a7460a6fc5e
|
||||
content/74: bcadfc362b69078beee0088e5936c98b
|
||||
content/75: 27eb346aea67fe81ed94fee3fa0e8bdf
|
||||
content/75: 3b6142572f73f6f6a58951d0048010f5
|
||||
content/76: a020f0e3fe6a3496a735851f230bce15
|
||||
content/77: ca39beca950791eb4a75a8da900114bf
|
||||
content/78: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/79: 2f5b4e0859d5314f6bf40be3ac13f641
|
||||
content/80: bcadfc362b69078beee0088e5936c98b
|
||||
content/81: bc8fff0a53b034c9383de92b55143450
|
||||
content/81: 2c579b11630f6b898e520e734588c2a2
|
||||
content/82: 67b79204dbe622e540a879638ef27f02
|
||||
content/83: aed07dcfb7bcbcb4f8e1297276888f04
|
||||
content/84: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/85: 42c3b4d062ae50f6ebaf9a7460a6fc5e
|
||||
content/86: bcadfc362b69078beee0088e5936c98b
|
||||
content/87: 4e1b6f9d3f1d760834a31d8b49faa01e
|
||||
content/87: 4373349c4b00e00a7efa918a02fd0859
|
||||
content/88: e0e21aa31b6b03674fb8e6a9167d809d
|
||||
content/89: 6d55eb60d0264d211cd8e027885738fa
|
||||
content/90: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/91: 42c3b4d062ae50f6ebaf9a7460a6fc5e
|
||||
content/92: bcadfc362b69078beee0088e5936c98b
|
||||
content/93: a31e86b6e03b7df7b9bd013f5c5b3d20
|
||||
content/93: 5050b0f0d60755c98d93630e06da0c72
|
||||
content/94: 8629b9c6f2ecbbacb248f9a65938555e
|
||||
content/95: f69ca030edb0f0c9970a51250fbe1699
|
||||
content/96: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/97: 42c3b4d062ae50f6ebaf9a7460a6fc5e
|
||||
content/98: bcadfc362b69078beee0088e5936c98b
|
||||
content/99: e2fc3018c16f1ae4f3e907fc33548d46
|
||||
content/99: cdaf2dce3d06cfa70cf438b12e769007
|
||||
content/100: e5d8665486fab89d1c60b9ab6cfe1c2e
|
||||
content/101: 62c4501130fb887500df574e0c09c20d
|
||||
content/102: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/103: 7e048124d93bb4cdcd6effb9b0c4b414
|
||||
content/104: bcadfc362b69078beee0088e5936c98b
|
||||
content/105: c463989e1754b17b2cc1a7b9fff68cb6
|
||||
content/105: 6fe8446ecb76f21e5fa718b65e7ed421
|
||||
content/106: 0b68d1e5df6bd6d9ea8be971c4f8bbbf
|
||||
content/107: 13876fa5c17849a15f6d54647feb6ae5
|
||||
content/108: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/109: 77a4b3016c40c377897e029411f1b8fd
|
||||
content/109: 1824d8209db86f3c1fa3f5beed26e05c
|
||||
content/110: bcadfc362b69078beee0088e5936c98b
|
||||
content/111: 77916aef5f86264c96b85ab874c3c447
|
||||
content/111: b3ec56f51a86f8eb02fa471b377171c5
|
||||
content/112: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/113: 29208f859f7c25898a8bb435d3e744d0
|
||||
54ec89df9800159df913e0955d2775e1:
|
||||
@@ -48222,103 +48224,103 @@ checksums:
|
||||
content/11: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/12: 78b6dd9f0a02aecf0d26bb37cd9a6854
|
||||
content/13: bcadfc362b69078beee0088e5936c98b
|
||||
content/14: 7311861512fa50f2c937d080692151e8
|
||||
content/14: 45e4b48c07557ffff5956d9466295197
|
||||
content/15: a29e8f758eeb0adc9d14cb3ecc9a3572
|
||||
content/16: 3bb1094fb4f6bbacf4503db791290728
|
||||
content/17: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/18: d185d2b7ceafeacce0f677a4649cb308
|
||||
content/19: bcadfc362b69078beee0088e5936c98b
|
||||
content/20: 4634d3f690b2b1977d4555b2d4eee739
|
||||
content/20: d25c6e706996660030e1ce66bcdb9e7e
|
||||
content/21: dd173220664de67767b3f0aad2808436
|
||||
content/22: 059484693528bd0f6324763d7f32abeb
|
||||
content/23: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/24: dfb2c2a7b6f5ddb86f9104defb6e8b5d
|
||||
content/25: bcadfc362b69078beee0088e5936c98b
|
||||
content/26: a88bd6e0708b4c3017be42eabc480947
|
||||
content/26: 8dbf76672372fa4f655305255bab0568
|
||||
content/27: 49e6762a44cc519ca70217ba8d90fbf7
|
||||
content/28: 77451909a80759c6ebb246a526e6fd7b
|
||||
content/29: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/30: 6edd84776ad45703537015af58f492cb
|
||||
content/31: bcadfc362b69078beee0088e5936c98b
|
||||
content/32: b6656b4d0cb132b838d73e758850e3f9
|
||||
content/32: fdba9cfce336484ab03fc2ac32bffea9
|
||||
content/33: a04ec00949bbb0a7c99bff8fd232e820
|
||||
content/34: 3b42cf913c5f403544540b6244dc8e2e
|
||||
content/35: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/36: bddd30707802c07aac61620721bfaf16
|
||||
content/37: bcadfc362b69078beee0088e5936c98b
|
||||
content/38: b622395040bf1aef193558d10a38c128
|
||||
content/38: fa2c581e6fb204f5ddbd0ffcbf0f7123
|
||||
content/39: 65de097e276f762b71d59fa7f9b0a207
|
||||
content/40: 013f52c249b5919fdb6d96700b25f379
|
||||
content/41: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/42: 8c33b1b3f984f011679b36b77801b936
|
||||
content/43: bcadfc362b69078beee0088e5936c98b
|
||||
content/44: c463989e1754b17b2cc1a7b9fff68cb6
|
||||
content/44: af1f849f131fe114138b638d3dc9edd7
|
||||
content/45: 353bd4e36dd70f288ff051157c0bbadb
|
||||
content/46: 9ee4b0cc8813aeea6b991dabb05c0b2b
|
||||
content/47: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/48: 93c01d42da765b2862a1c90e5c207f3a
|
||||
content/49: bcadfc362b69078beee0088e5936c98b
|
||||
content/50: cb25800f3f5e9a5688769ba1f1d101fb
|
||||
content/50: 6af34cd425be8517ea6fb6a014621329
|
||||
content/51: 0440d7abaca1a48b2ce63b48c97444a1
|
||||
content/52: 4cb6ac8a34a8cc0d87a85a45dd9382b9
|
||||
content/53: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/54: df651aea48c483f6163ae49e4f1fda5a
|
||||
content/55: bcadfc362b69078beee0088e5936c98b
|
||||
content/56: 90bd63383b8ba686aa71a192a945344a
|
||||
content/56: cb3c2f1293d213df984c1273c5a4d14b
|
||||
content/57: 0c321044b21a59eec7b04f91d6ad521f
|
||||
content/58: d8a0a8ae22b5c2a0664f1172adf2c1d1
|
||||
content/59: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/60: fcef3a88d9de7bfa5e308e4cece9136e
|
||||
content/61: bcadfc362b69078beee0088e5936c98b
|
||||
content/62: d6ec19129a153bc0667e76edb9c2953d
|
||||
content/62: b7ed771bffa972a584913797325ee02b
|
||||
content/63: ce48c0560ca3ddaad402f2bdd9d459f3
|
||||
content/64: d96d5d108333391af5bc3ab6425c9f29
|
||||
content/65: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/66: be92911dda52b245d2fda5b0c54527dc
|
||||
content/67: bcadfc362b69078beee0088e5936c98b
|
||||
content/68: 77916aef5f86264c96b85ab874c3c447
|
||||
content/68: 43b7bd9c584decffb62fdc75409675a6
|
||||
content/69: 85eba1d62b7cdd083d0a4991169d7162
|
||||
content/70: d4dd284c2b688770d5c732a7e6dff3f0
|
||||
content/71: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/72: ad3910637d3e53fef449e8a5aa74b1ef
|
||||
content/73: bcadfc362b69078beee0088e5936c98b
|
||||
content/74: 3f6664c231057ceadd9ac8cba5332b3e
|
||||
content/74: 56a1ecf365a95c06e42fcf80dc28fb31
|
||||
content/75: 4936debbcf6ba547fa7c9265f7dcf40f
|
||||
content/76: 17a43ca41485e2f78ef7987c78a52e8b
|
||||
content/77: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/78: b87a8359c3277447f29050ef280f4ceb
|
||||
content/79: bcadfc362b69078beee0088e5936c98b
|
||||
content/80: be30e8adfbab208f4b7b6fedc0559c19
|
||||
content/80: a1414f8625473aca0b4635ebd8e50c34
|
||||
content/81: 72de310caecaf16e8e74ff37a7fa46b0
|
||||
content/82: 4738fdcfd44aa60f5bfd7f484be36360
|
||||
content/83: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/84: 3ff5cc143dcf2514e730b4f68757a6cb
|
||||
content/85: bcadfc362b69078beee0088e5936c98b
|
||||
content/86: 08aaa5e1b8c8e8c146f68228e6d53792
|
||||
content/86: d05aa3c271674d80fd34fe5d7c0378fd
|
||||
content/87: a72a19e1aff8ea71d24df98f312d9ada
|
||||
content/88: 0a0fecc6d1a70497410160ee0ea9e757
|
||||
content/89: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/90: d71b6bb8e2dd6ce98101aec6a1dd77f2
|
||||
content/91: bcadfc362b69078beee0088e5936c98b
|
||||
content/92: 2e1b6ed9ba71ee98540f582bf5decd41
|
||||
content/92: 55235554f8ac9d06bebf6827bb5a67a4
|
||||
content/93: b37060e61c0433052dd02939aa60e412
|
||||
content/94: c2184e718b1015489f82854fe48e6a37
|
||||
content/95: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/96: 574cc410d9df4f37e7dc291491e13fe6
|
||||
content/97: bcadfc362b69078beee0088e5936c98b
|
||||
content/98: b06f1d576f491777f39a47401c77f268
|
||||
content/98: 7f3282d2c35a26a84c97a5c8aec51bd8
|
||||
content/99: 5070ca6b1d2cbd4f8450b15e8f46e446
|
||||
content/100: 3edfc5e9d7138730bb73863616f3435e
|
||||
content/101: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/102: 8d5f23e56e4606bacc7ac1f3b1cbe86b
|
||||
content/103: bcadfc362b69078beee0088e5936c98b
|
||||
content/104: 52f075cb7459e5cf8daf1f3e37ecd36b
|
||||
content/104: c3013a47beb9b8db755de36615056c13
|
||||
content/105: 82c709054f9e80781baa7d678ff03b24
|
||||
content/106: 5a38d9ff2a65641f8f13e91db263cc9e
|
||||
content/107: 371d0e46b4bd2c23f559b8bc112f6955
|
||||
content/108: 71b47f694d04e8f1a8fa22723539fe47
|
||||
content/109: bcadfc362b69078beee0088e5936c98b
|
||||
content/110: 45b9563e3bcc50f89b39b015a9d46114
|
||||
content/110: 4a0585b138348fe3806097c0542e8cb2
|
||||
content/111: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/112: cbeffb4f92b38461ac320bc6fe7f7ef0
|
||||
d58d2e8b125a994bcfb20ff98cfef0ad:
|
||||
@@ -49300,3 +49302,17 @@ checksums:
|
||||
content/42: dc2cfed837ea55adfa23bd7c87d5299d
|
||||
content/43: b3f310d5ef115bea5a8b75bf25d7ea9a
|
||||
content/44: df2ef65659b8ea0a13916358943f965b
|
||||
ebed3bd73520bf81399749586796f9d0:
|
||||
meta/title: 1763bebd6001500cdfc1b5127b0c1cde
|
||||
content/0: eb0ed7078f192304703144f4cac3442f
|
||||
content/1: ba5ba29787a0eb35c46dacb3544bafe1
|
||||
content/2: 5ed74bf0e91235f71eeceb25712ad2d3
|
||||
content/3: 0441638444240cd20a6c69ea1d3afbb1
|
||||
content/4: ef102e10f1402df7290680c1e9df8a5e
|
||||
content/5: 95afa83a30cb01724b932b19dd69f20b
|
||||
content/6: 8ebc5e005f61d253c006824168abaf22
|
||||
content/7: df81a49b54d378523fb74aa0b0fb8be1
|
||||
content/8: c5fb77d31bae86aa85f2b2b84ce0beab
|
||||
content/9: 7a3be8a3771ee428ecf09008e42c0e2e
|
||||
content/10: 42e4caf9b036a8d7726a8968f3ed201f
|
||||
content/11: e74f8ee79105babdaa8dfec520ecdf74
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"dependencies": {
|
||||
"@tabler/icons-react": "^3.31.0",
|
||||
"@vercel/og": "^0.6.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"fumadocs-core": "16.2.3",
|
||||
"fumadocs-mdx": "14.1.0",
|
||||
|
||||
BIN
apps/docs/public/static/blocks/rss.png
Normal file
BIN
apps/docs/public/static/blocks/rss.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -1,777 +0,0 @@
|
||||
# Sim App Architecture Guidelines
|
||||
|
||||
You are building features in the Sim app following the architecture. This file defines the patterns, structures, and conventions you must follow.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Single Responsibility Principle**: Each component, hook, and store should have one clear purpose
|
||||
2. **Composition Over Complexity**: Break down complex logic into smaller, composable pieces
|
||||
3. **Type Safety First**: Use TypeScript interfaces for all props, state, and return types
|
||||
4. **Predictable State Management**: Use Zustand for global state, local state for UI-only concerns
|
||||
5. **Performance by Default**: Use useMemo, useCallback, and refs appropriately
|
||||
6. **Accessibility**: Include semantic HTML and ARIA attributes
|
||||
7. **Documentation**: Use TSDoc for all public interfaces and keep it in sync with code changes
|
||||
|
||||
---
|
||||
|
||||
## File Organization
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
feature/
|
||||
├── components/ # Feature components
|
||||
│ ├── sub-feature/ # Sub-feature with its own components
|
||||
│ │ ├── component.tsx
|
||||
│ │ └── index.ts
|
||||
├── hooks/ # Custom hooks for feature logic
|
||||
│ ├── use-feature-logic.ts
|
||||
│ └── use-another-hook.ts
|
||||
└── feature.tsx # Main feature component
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- **Components**: PascalCase with descriptive names (`WorkflowList`, `TriggerPanel`)
|
||||
- **Hooks**: camelCase with `use` prefix (`useWorkflowOperations`, `usePanelResize`)
|
||||
- **Files**: kebab-case matching export name (`workflow-list.tsx`, `use-panel-resize.ts`)
|
||||
- **Stores**: kebab-case in stores/ directory (`sidebar/store.ts`, `workflows/registry/store.ts`)
|
||||
- **Constants**: SCREAMING_SNAKE_CASE at module level
|
||||
- **Interfaces**: PascalCase with descriptive suffix (`WorkflowListProps`, `UseWorkspaceManagementProps`)
|
||||
|
||||
---
|
||||
|
||||
## Component Architecture
|
||||
|
||||
### Component Structure Template
|
||||
|
||||
```typescript
|
||||
'use client' // Only if using hooks like useState, useEffect, etc.
|
||||
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
// Other imports organized: external, internal paths, relative
|
||||
|
||||
/**
|
||||
* Constants - Define at module level before component
|
||||
*/
|
||||
const DEFAULT_VALUE = 100
|
||||
const MIN_VALUE = 50
|
||||
const MAX_VALUE = 200
|
||||
|
||||
const CONFIG = {
|
||||
SPACING: 8,
|
||||
ITEM_HEIGHT: 25,
|
||||
} as const
|
||||
|
||||
interface ComponentProps {
|
||||
/** Description of prop */
|
||||
requiredProp: string
|
||||
/** Description with default noted */
|
||||
optionalProp?: boolean
|
||||
onAction?: (id: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Component description explaining purpose and key features.
|
||||
* Mention important integrations, hooks, or patterns used.
|
||||
*
|
||||
* @param props - Component props
|
||||
* @returns JSX description
|
||||
*/
|
||||
export function ComponentName({
|
||||
requiredProp,
|
||||
optionalProp = false,
|
||||
onAction,
|
||||
}: ComponentProps) {
|
||||
// 1. Refs first
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// 2. External hooks (router, params, context)
|
||||
const params = useParams()
|
||||
|
||||
// 3. Store hooks
|
||||
const { state, actions } = useStore()
|
||||
|
||||
// 4. Custom hooks (your feature hooks)
|
||||
const { data, isLoading } = useCustomHook({ requiredProp })
|
||||
|
||||
// 5. Local state (UI-only concerns)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
// 6. Derived/computed values with useMemo
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter(item => item.active)
|
||||
}, [data])
|
||||
|
||||
// 7. Callbacks with useCallback
|
||||
const handleClick = useCallback((id: string) => {
|
||||
onAction?.(id)
|
||||
}, [onAction])
|
||||
|
||||
// 8. Effects
|
||||
useEffect(() => {
|
||||
// Setup logic
|
||||
return () => {
|
||||
// Cleanup logic
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 9. Render helpers (if complex)
|
||||
const renderItem = useCallback((item: Item) => (
|
||||
<div key={item.id}>{item.name}</div>
|
||||
), [])
|
||||
|
||||
// 10. Return JSX
|
||||
return (
|
||||
<div ref={containerRef} className='...' aria-label='...'>
|
||||
{/* Section comments for clarity */}
|
||||
{/* Header */}
|
||||
<header>...</header>
|
||||
|
||||
{/* Content */}
|
||||
<main>...</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Component Rules
|
||||
|
||||
1. **Client Components**: Add `'use client'` directive when using React hooks
|
||||
2. **Props Interface**: Always define TypeScript interface, even for simple props
|
||||
3. **TSDoc Required and Up-to-Date**: Include description, @param, and @returns. Update TSDoc whenever props, behavior, or side effects change (including additions and deletions).
|
||||
4. **Constants**: Extract magic numbers and config to module-level constants using `as const`
|
||||
5. **No Inline Styles**: Use Tailwind classes exclusively (CSS variables for dynamic values)
|
||||
6. **Section Comments**: Use comments to mark logical sections of JSX
|
||||
7. **Semantic HTML**: Use appropriate elements (`aside`, `nav`, `article`, etc.)
|
||||
8. **ARIA Attributes**: Include `aria-label`, `aria-orientation`, `role` where appropriate
|
||||
9. **Refs for DOM**: Use refs for direct DOM access, not state
|
||||
10. **Callback Props**: Always use optional chaining for callback props (`onAction?.(...)`)
|
||||
|
||||
---
|
||||
|
||||
## Custom Hooks Architecture
|
||||
|
||||
### Hook Structure Template
|
||||
|
||||
```typescript
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('useFeatureName')
|
||||
|
||||
/**
|
||||
* Constants specific to this hook
|
||||
*/
|
||||
const DEFAULT_CONFIG = {
|
||||
timeout: 1000,
|
||||
retries: 3,
|
||||
} as const
|
||||
|
||||
interface UseFeatureNameProps {
|
||||
/** Description of required prop */
|
||||
id: string
|
||||
/** Optional callback fired on success */
|
||||
onSuccess?: (result: Result) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom hook to [clear description of purpose].
|
||||
* [Additional context about what it manages or coordinates].
|
||||
*
|
||||
* @param props - Configuration object containing id and callbacks
|
||||
* @returns Feature state and operations
|
||||
*/
|
||||
export function useFeatureName({ id, onSuccess }: UseFeatureNameProps) {
|
||||
// 1. Refs (to avoid dependency issues)
|
||||
const idRef = useRef(id)
|
||||
const onSuccessRef = useRef(onSuccess)
|
||||
|
||||
// 2. State
|
||||
const [data, setData] = useState<Data | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
// 3. Update refs when values change
|
||||
useEffect(() => {
|
||||
idRef.current = id
|
||||
onSuccessRef.current = onSuccess
|
||||
}, [id, onSuccess])
|
||||
|
||||
// 4. Operations with useCallback (stable references)
|
||||
const fetchData = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await fetch(`/api/data/${idRef.current}`)
|
||||
const result = await response.json()
|
||||
setData(result)
|
||||
onSuccessRef.current?.(result)
|
||||
logger.info('Data fetched successfully', { id: idRef.current })
|
||||
} catch (err) {
|
||||
const error = err as Error
|
||||
setError(error)
|
||||
logger.error('Failed to fetch data', { error })
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, []) // Empty deps because using refs
|
||||
|
||||
const updateData = useCallback(async (newData: Partial<Data>) => {
|
||||
try {
|
||||
logger.info('Updating data', { id: idRef.current, newData })
|
||||
const response = await fetch(`/api/data/${idRef.current}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(newData),
|
||||
})
|
||||
const result = await response.json()
|
||||
setData(result)
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error('Failed to update data', { error: err })
|
||||
return false
|
||||
}
|
||||
}, [])
|
||||
|
||||
// 5. Effects
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchData()
|
||||
}
|
||||
}, [id, fetchData])
|
||||
|
||||
// 6. Return object - group by state and operations
|
||||
return {
|
||||
// State
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Operations
|
||||
fetchData,
|
||||
updateData,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Hook Rules
|
||||
|
||||
1. **Single Responsibility**: Each hook manages one concern (data fetching, resize, navigation)
|
||||
2. **Props Interface**: Define TypeScript interface for all parameters
|
||||
3. **TSDoc Required and Up-to-Date**: Include clear description, @param, and @returns. Update TSDoc whenever inputs, outputs, behavior, or side effects change (including additions and deletions).
|
||||
4. **Logger Usage**: Import and use logger instead of console.log
|
||||
5. **Refs for Stable Deps**: Use refs to avoid recreating callbacks unnecessarily
|
||||
6. **useCallback Always**: Wrap all returned functions in useCallback
|
||||
7. **Grouped Returns**: Return object with comments separating State and Operations
|
||||
8. **Error Handling**: Always try/catch async operations and log errors
|
||||
9. **Loading States**: Track loading, error states for async operations
|
||||
10. **Dependency Arrays**: Be explicit and correct with all dependency arrays
|
||||
|
||||
---
|
||||
|
||||
## Store Architecture (Zustand)
|
||||
|
||||
### Store Structure Template
|
||||
|
||||
```typescript
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
|
||||
/**
|
||||
* Store state interface
|
||||
*/
|
||||
interface FeatureState {
|
||||
// State properties
|
||||
items: Item[]
|
||||
activeId: string | null
|
||||
isLoading: boolean
|
||||
|
||||
// Actions
|
||||
setItems: (items: Item[]) => void
|
||||
setActiveId: (id: string | null) => void
|
||||
addItem: (item: Item) => void
|
||||
removeItem: (id: string) => void
|
||||
clearState: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants - Configuration values
|
||||
*/
|
||||
const DEFAULT_CONFIG = {
|
||||
maxItems: 100,
|
||||
cacheTime: 3600,
|
||||
} as const
|
||||
|
||||
/**
|
||||
* Initial state factory
|
||||
*/
|
||||
const createInitialState = () => ({
|
||||
items: [],
|
||||
activeId: null,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
/**
|
||||
* Feature store managing [description].
|
||||
* [Additional context about what this store coordinates].
|
||||
*/
|
||||
export const useFeatureStore = create<FeatureState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
...createInitialState(),
|
||||
|
||||
setItems: (items) => set({ items }),
|
||||
|
||||
setActiveId: (id) => set({ activeId: id }),
|
||||
|
||||
addItem: (item) =>
|
||||
set((state) => ({
|
||||
items: [...state.items, item].slice(-DEFAULT_CONFIG.maxItems),
|
||||
})),
|
||||
|
||||
removeItem: (id) =>
|
||||
set((state) => ({
|
||||
items: state.items.filter((item) => item.id !== id),
|
||||
activeId: state.activeId === id ? null : state.activeId,
|
||||
})),
|
||||
|
||||
clearState: () => set(createInitialState()),
|
||||
}),
|
||||
{
|
||||
name: 'feature-state',
|
||||
// Optionally customize what to persist
|
||||
partialize: (state) => ({
|
||||
items: state.items,
|
||||
activeId: state.activeId,
|
||||
}),
|
||||
onRehydrateStorage: () => (state) => {
|
||||
// Validate and transform persisted state if needed
|
||||
if (state) {
|
||||
// Enforce constraints
|
||||
if (state.items.length > DEFAULT_CONFIG.maxItems) {
|
||||
state.items = state.items.slice(-DEFAULT_CONFIG.maxItems)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### Store Rules
|
||||
|
||||
1. **Interface First**: Define TypeScript interface including both state and actions
|
||||
2. **Constants**: Extract configuration values to module-level constants
|
||||
3. **TSDoc on Store**: Document what the store manages
|
||||
4. **Persist Strategically**: Only persist what's needed across sessions
|
||||
5. **Validation**: Use onRehydrateStorage to validate persisted state
|
||||
6. **Immutable Updates**: Use set() with new objects/arrays, never mutate
|
||||
7. **Derived State**: Use getters or selectors, not stored computed values
|
||||
8. **CSS Variables**: Update CSS variables in setters for hydration-safe dynamic styles
|
||||
9. **Cleanup Actions**: Provide clear/reset actions for state cleanup
|
||||
10. **Functional Updates**: Use `set((state) => ...)` when new state depends on old state
|
||||
|
||||
---
|
||||
|
||||
## State Management Strategy
|
||||
|
||||
### When to Use Local State (useState)
|
||||
|
||||
- UI-only concerns (dropdown open, hover states, form inputs)
|
||||
- Component-scoped state not needed elsewhere
|
||||
- Temporary state that doesn't need persistence
|
||||
|
||||
### When to Use Zustand Store
|
||||
|
||||
- Shared state across multiple components
|
||||
- State that needs persistence (localStorage)
|
||||
- Global application state (user, theme, settings)
|
||||
- Complex state with many actions/reducers
|
||||
|
||||
### When to Use Refs (useRef)
|
||||
|
||||
- DOM element references
|
||||
- Avoiding dependency issues in hooks
|
||||
- Storing mutable values that don't trigger re-renders
|
||||
- Accessing latest props/state in callbacks without recreating them
|
||||
|
||||
---
|
||||
|
||||
## CSS and Styling
|
||||
|
||||
### CSS Variables for Dynamic Styles
|
||||
|
||||
Use CSS variables for values that need to persist across hydration:
|
||||
|
||||
```typescript
|
||||
// In store setter
|
||||
setSidebarWidth: (width) => {
|
||||
const clampedWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, width))
|
||||
set({ sidebarWidth: clampedWidth })
|
||||
|
||||
// Update CSS variable for immediate visual feedback
|
||||
if (typeof window !== 'undefined') {
|
||||
document.documentElement.style.setProperty('--sidebar-width', `${clampedWidth}px`)
|
||||
}
|
||||
}
|
||||
|
||||
// In component
|
||||
<aside
|
||||
className='sidebar-container'
|
||||
style={{ width: 'var(--sidebar-width)' }}
|
||||
>
|
||||
```
|
||||
|
||||
### Tailwind Classes
|
||||
|
||||
1. **No Inline Styles**: Use Tailwind utility classes exclusively
|
||||
2. **Dark Mode**: Include dark mode variants only when the value differs from light mode
|
||||
3. **No Duplicate Dark Classes**: Never add a `dark:` class when the value is identical to the light mode class (e.g., `text-[var(--text-primary)] dark:text-[var(--text-primary)]` is redundant - just use `text-[var(--text-primary)]`)
|
||||
4. **Exact Values**: Use exact values from design system (`text-[14px]`, `h-[25px]`)
|
||||
5. **cn for Conditionals**: Use `cn()` from `@/lib/utils` for conditional classes (wraps clsx + tailwind-merge for conflict resolution)
|
||||
6. **Consistent Spacing**: Use spacing tokens (`gap-[8px]`, `px-[14px]`)
|
||||
7. **Transitions**: Add transitions for interactive states (`transition-colors`)
|
||||
8. **Prefer px units**: Use arbitrary px values over scale utilities (e.g., `px-[4px]` instead of `px-1`)
|
||||
|
||||
```typescript
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'base-classes that-always-apply',
|
||||
isActive && 'active-state-classes',
|
||||
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-accent'
|
||||
)}
|
||||
>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Patterns
|
||||
|
||||
### Interface Conventions
|
||||
|
||||
```typescript
|
||||
// Component props
|
||||
interface ComponentNameProps {
|
||||
requiredProp: string
|
||||
optionalProp?: boolean
|
||||
}
|
||||
|
||||
// Hook props
|
||||
interface UseHookNameProps {
|
||||
id: string
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
// Store state
|
||||
interface FeatureState {
|
||||
data: Data[]
|
||||
isLoading: boolean
|
||||
actions: () => void
|
||||
}
|
||||
|
||||
// Return types (if complex)
|
||||
interface UseHookNameReturn {
|
||||
state: State
|
||||
actions: Actions
|
||||
}
|
||||
```
|
||||
|
||||
### Type Safety Rules
|
||||
|
||||
1. **No `any`**: Use proper types or `unknown` with type guards
|
||||
2. **Props Interface**: Always define, even for simple components
|
||||
3. **Callback Types**: Define full signature including parameters and return type
|
||||
4. **Generic Types**: Use generics for reusable components/hooks
|
||||
5. **Const Assertions**: Use `as const` for constant objects/arrays
|
||||
6. **Type Guards**: Create type guards for runtime checks
|
||||
7. **Ref Types**: Explicitly type refs (`useRef<HTMLDivElement>(null)`)
|
||||
|
||||
---
|
||||
|
||||
## Performance Patterns
|
||||
|
||||
### Memoization
|
||||
|
||||
```typescript
|
||||
// useMemo for expensive computations
|
||||
const sortedItems = useMemo(() => {
|
||||
return items.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}, [items])
|
||||
|
||||
// useCallback for functions passed as props
|
||||
const handleClick = useCallback((id: string) => {
|
||||
onItemClick?.(id)
|
||||
}, [onItemClick])
|
||||
|
||||
// useCallback for render functions
|
||||
const renderItem = useCallback((item: Item) => (
|
||||
<ItemComponent key={item.id} item={item} onClick={handleClick} />
|
||||
), [handleClick])
|
||||
```
|
||||
|
||||
### When to Memoize
|
||||
|
||||
1. **useMemo**: Expensive calculations, filtering/sorting large arrays, object creation in render
|
||||
2. **useCallback**: Functions passed to child components, dependencies in other hooks, event handlers used in effects
|
||||
3. **Don't Over-Memoize**: Simple calculations, primitives, or functions not passed down
|
||||
|
||||
### Refs for Avoiding Recreations
|
||||
|
||||
```typescript
|
||||
// Pattern: Use refs to avoid function recreations
|
||||
const onSuccessRef = useRef(onSuccess)
|
||||
|
||||
useEffect(() => {
|
||||
onSuccessRef.current = onSuccess
|
||||
}, [onSuccess])
|
||||
|
||||
const stableCallback = useCallback(() => {
|
||||
// Use ref so this callback never needs to change
|
||||
onSuccessRef.current?.()
|
||||
}, []) // Empty deps!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logging and Debugging
|
||||
|
||||
### Logger Usage
|
||||
|
||||
```typescript
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('ComponentName')
|
||||
|
||||
// Use throughout component/hook
|
||||
logger.info('User action', { userId, action })
|
||||
logger.warn('Potential issue', { details })
|
||||
logger.error('Operation failed', { error })
|
||||
```
|
||||
|
||||
### Logging Rules
|
||||
|
||||
1. **No console.log**: Use logger.info/warn/error instead
|
||||
2. **Logger Per File**: Create logger with component/hook name
|
||||
3. **Structured Logging**: Pass objects with context, not just strings
|
||||
4. **Log Levels**:
|
||||
- `info`: Normal operations, user actions, state changes
|
||||
- `warn`: Unusual but handled situations, deprecations
|
||||
- `error`: Failures, exceptions, errors that need attention
|
||||
|
||||
---
|
||||
|
||||
## Linting and Formatting
|
||||
|
||||
### Automated Linting
|
||||
|
||||
**Do not manually fix linting errors.** The project uses automated linting tools that should handle formatting and style issues.
|
||||
|
||||
### Rules
|
||||
|
||||
1. **No Manual Fixes**: Do not attempt to manually reorder CSS classes, fix formatting, or address linter warnings
|
||||
2. **Use Automated Tools**: If linting errors need to be fixed, run `bun run lint` to let the automated tools handle it
|
||||
3. **Focus on Logic**: Concentrate on functionality, TypeScript correctness, and architectural patterns
|
||||
4. **Let Tools Handle Style**: Biome and other linters will automatically format code according to project standards
|
||||
|
||||
### When Linting Matters
|
||||
|
||||
- **Syntax Errors**: Fix actual syntax errors that prevent compilation
|
||||
- **Type Errors**: Address TypeScript type errors that indicate logic issues
|
||||
- **Ignore Style Warnings**: CSS class order, formatting preferences, etc. will be handled by tooling
|
||||
|
||||
```bash
|
||||
# If linting is required
|
||||
bun run lint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Code Quality Checklist
|
||||
|
||||
Before considering a component/hook complete, verify:
|
||||
|
||||
### Documentation
|
||||
- [ ] TSDoc in sync with implementation after any change (params/returns/behavior/throws)
|
||||
- [ ] TSDoc comment on component/hook/store
|
||||
- [ ] Props interface documented with /** */ comments
|
||||
- [ ] Complex logic explained with inline comments
|
||||
- [ ] Section comments in JSX for clarity
|
||||
|
||||
### TypeScript
|
||||
- [ ] All props have interface defined
|
||||
- [ ] No `any` types used
|
||||
- [ ] Refs properly typed
|
||||
- [ ] Return types explicit for complex hooks
|
||||
|
||||
### Performance
|
||||
- [ ] useMemo for expensive computations
|
||||
- [ ] useCallback for functions passed as props
|
||||
- [ ] Refs used to avoid unnecessary recreations
|
||||
- [ ] No unnecessary re-renders
|
||||
|
||||
### Hooks
|
||||
- [ ] Correct dependency arrays
|
||||
- [ ] Cleanup in useEffect return functions
|
||||
- [ ] Stable callback references with useCallback
|
||||
- [ ] Logic extracted to custom hooks when reusable
|
||||
|
||||
### Styling
|
||||
- [ ] No styles attributes (use className with Tailwind)
|
||||
- [ ] Dark mode variants only when values differ from light mode
|
||||
- [ ] No duplicate dark: classes with identical values
|
||||
- [ ] Consistent spacing using design tokens
|
||||
- [ ] cn() for conditional classes
|
||||
|
||||
### Accessibility
|
||||
- [ ] Semantic HTML elements
|
||||
- [ ] ARIA labels and roles where needed
|
||||
- [ ] Keyboard navigation support
|
||||
- [ ] Focus management
|
||||
|
||||
### State Management
|
||||
- [ ] Local state for UI-only concerns
|
||||
- [ ] Zustand for shared/persisted state
|
||||
- [ ] No duplicate state
|
||||
- [ ] Clear state update patterns
|
||||
|
||||
### Error Handling
|
||||
- [ ] try/catch around async operations
|
||||
- [ ] Error states tracked and displayed
|
||||
- [ ] Loading states for async actions
|
||||
- [ ] Failures logged with context
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Don't Do This
|
||||
|
||||
```typescript
|
||||
// ❌ Inline styles
|
||||
<div style={{ width: 200, marginTop: 10 }}>
|
||||
|
||||
// ❌ Duplicate dark mode classes (same value as light mode)
|
||||
<div className='text-[var(--text-primary)] dark:text-[var(--text-primary)]'>
|
||||
<div className='bg-[var(--surface-9)] dark:bg-[var(--surface-9)]'>
|
||||
<div className='hover:bg-[var(--border)] dark:hover:bg-[var(--border)]'>
|
||||
|
||||
// ❌ console.log
|
||||
console.log('Debug info')
|
||||
|
||||
// ❌ any type
|
||||
const handleClick = (e: any) => {}
|
||||
|
||||
// ❌ Missing dependencies
|
||||
useEffect(() => {
|
||||
doSomething(prop)
|
||||
}, []) // Missing prop!
|
||||
|
||||
// ❌ Mutating state
|
||||
const handleAdd = () => {
|
||||
items.push(newItem) // Mutating!
|
||||
setItems(items)
|
||||
}
|
||||
|
||||
// ❌ No error handling
|
||||
const fetchData = async () => {
|
||||
const data = await fetch('/api/data')
|
||||
setData(data)
|
||||
}
|
||||
|
||||
// ❌ Complex logic in component
|
||||
export function Component() {
|
||||
const [data, setData] = useState([])
|
||||
useEffect(() => {
|
||||
// 50 lines of complex logic
|
||||
}, [])
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Do This Instead
|
||||
|
||||
```typescript
|
||||
// ✅ Tailwind classes
|
||||
<div className='w-[200px] mt-[10px]'>
|
||||
|
||||
// ✅ No duplicate dark classes - CSS variables already handle theming
|
||||
<div className='text-[var(--text-primary)]'>
|
||||
<div className='bg-[var(--surface-9)]'>
|
||||
<div className='hover:bg-[var(--border)]'>
|
||||
|
||||
// ✅ Only add dark: when values differ between modes
|
||||
<div className='bg-[var(--surface-6)] dark:bg-[var(--surface-9)]'>
|
||||
|
||||
// ✅ Logger
|
||||
logger.info('Debug info', { context })
|
||||
|
||||
// ✅ Proper types
|
||||
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {}
|
||||
|
||||
// ✅ Complete dependencies
|
||||
useEffect(() => {
|
||||
doSomething(prop)
|
||||
}, [prop])
|
||||
|
||||
// ✅ Immutable updates
|
||||
const handleAdd = () => {
|
||||
setItems([...items, newItem])
|
||||
}
|
||||
|
||||
// ✅ Error handling
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/data')
|
||||
if (!response.ok) throw new Error('Failed to fetch')
|
||||
const data = await response.json()
|
||||
setData(data)
|
||||
} catch (error) {
|
||||
logger.error('Fetch failed', { error })
|
||||
setError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Extract to custom hook
|
||||
export function Component() {
|
||||
const { data, isLoading, error } = useFeatureData()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples from Codebase
|
||||
|
||||
Study these files as reference implementations:
|
||||
|
||||
### Components
|
||||
- `sidebar-new.tsx` - Main component structure, hook composition
|
||||
- `workflow-list.tsx` - Complex component with drag-drop, memoization
|
||||
- `blocks.tsx` - Simple panel component with resize
|
||||
- `triggers.tsx` - Similar panel pattern
|
||||
|
||||
### Hooks
|
||||
- `use-workspace-management.ts` - Complex hook with multiple operations, refs pattern
|
||||
- `use-sidebar-resize.ts` - Simple focused hook with event listeners
|
||||
- `use-workflow-operations.ts` - Hook coordinating store and navigation
|
||||
- `use-panel-resize.ts` - Shared resize logic pattern
|
||||
|
||||
### Stores
|
||||
- `stores/sidebar/store.ts` - Persist middleware, CSS variables, validation
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This architecture prioritizes:
|
||||
|
||||
1. **Separation of Concerns**: Components render, hooks contain logic, stores manage state
|
||||
2. **Type Safety**: TypeScript everywhere with no escape hatches
|
||||
3. **Performance**: Smart use of memoization and refs
|
||||
4. **Maintainability**: Clear structure, documentation, and consistent patterns
|
||||
5. **Developer Experience**: Logging, error handling, and clear interfaces
|
||||
|
||||
When in doubt, follow the patterns established in the sidebar-new component family.
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
ENTERPRISE_PLAN_FEATURES,
|
||||
PRO_PLAN_FEATURES,
|
||||
TEAM_PLAN_FEATURES,
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components-new/settings-modal/components/subscription/plan-configs'
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/plan-configs'
|
||||
|
||||
const logger = createLogger('LandingPricing')
|
||||
|
||||
|
||||
@@ -206,6 +206,9 @@
|
||||
--terminal-status-info-bg: #f5f5f4; /* stone-100 */
|
||||
--terminal-status-info-border: #a8a29e; /* stone-400 */
|
||||
--terminal-status-info-color: #57534e; /* stone-600 */
|
||||
--terminal-status-warning-bg: #fef9e7;
|
||||
--terminal-status-warning-border: #f5c842;
|
||||
--terminal-status-warning-color: #a16207;
|
||||
}
|
||||
.dark {
|
||||
/* Neutrals (surfaces) */
|
||||
@@ -336,6 +339,9 @@
|
||||
--terminal-status-info-bg: #383838;
|
||||
--terminal-status-info-border: #686868;
|
||||
--terminal-status-info-color: #b7b7b7;
|
||||
--terminal-status-warning-bg: #3d3520;
|
||||
--terminal-status-warning-border: #5c4d1f;
|
||||
--terminal-status-warning-color: #d4a72c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,42 @@ import { refreshOAuthToken } from '@/lib/oauth/oauth'
|
||||
|
||||
const logger = createLogger('OAuthUtilsAPI')
|
||||
|
||||
interface AccountInsertData {
|
||||
id: string
|
||||
userId: string
|
||||
providerId: string
|
||||
accountId: string
|
||||
accessToken: string
|
||||
scope: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
refreshToken?: string
|
||||
idToken?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely inserts an account record, handling duplicate constraint violations gracefully.
|
||||
* If a duplicate is detected (unique constraint violation), logs a warning and returns success.
|
||||
*/
|
||||
export async function safeAccountInsert(
|
||||
data: AccountInsertData,
|
||||
context: { provider: string; identifier?: string }
|
||||
): Promise<void> {
|
||||
try {
|
||||
await db.insert(account).values(data)
|
||||
logger.info(`Created new ${context.provider} account for user`, { userId: data.userId })
|
||||
} catch (error: any) {
|
||||
if (error?.code === '23505') {
|
||||
logger.error(`Duplicate ${context.provider} account detected, credential already exists`, {
|
||||
userId: data.userId,
|
||||
identifier: context.identifier,
|
||||
})
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user ID based on either a session or a workflow ID
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getBaseUrl } from '@/lib/core/utils/urls'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { safeAccountInsert } from '@/app/api/auth/oauth/utils'
|
||||
|
||||
const logger = createLogger('ShopifyStore')
|
||||
|
||||
@@ -66,14 +67,20 @@ export async function GET(request: NextRequest) {
|
||||
await db.update(account).set(accountData).where(eq(account.id, existing.id))
|
||||
logger.info('Updated existing Shopify account', { accountId: existing.id })
|
||||
} else {
|
||||
await db.insert(account).values({
|
||||
id: `shopify_${session.user.id}_${Date.now()}`,
|
||||
userId: session.user.id,
|
||||
providerId: 'shopify',
|
||||
...accountData,
|
||||
createdAt: now,
|
||||
})
|
||||
logger.info('Created new Shopify account for user', { userId: session.user.id })
|
||||
await safeAccountInsert(
|
||||
{
|
||||
id: `shopify_${session.user.id}_${Date.now()}`,
|
||||
userId: session.user.id,
|
||||
providerId: 'shopify',
|
||||
accountId: accountData.accountId,
|
||||
accessToken: accountData.accessToken,
|
||||
scope: accountData.scope,
|
||||
idToken: accountData.idToken,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
{ provider: 'Shopify', identifier: shopDomain }
|
||||
)
|
||||
}
|
||||
|
||||
const returnUrl = request.cookies.get('shopify_return_url')?.value
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { env } from '@/lib/core/config/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { safeAccountInsert } from '@/app/api/auth/oauth/utils'
|
||||
import { db } from '@/../../packages/db'
|
||||
import { account } from '@/../../packages/db/schema'
|
||||
|
||||
@@ -67,16 +68,19 @@ export async function POST(request: NextRequest) {
|
||||
})
|
||||
.where(eq(account.id, existing.id))
|
||||
} else {
|
||||
await db.insert(account).values({
|
||||
id: `trello_${session.user.id}_${Date.now()}`,
|
||||
userId: session.user.id,
|
||||
providerId: 'trello',
|
||||
accountId: trelloUser.id,
|
||||
accessToken: token,
|
||||
scope: 'read,write',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
await safeAccountInsert(
|
||||
{
|
||||
id: `trello_${session.user.id}_${Date.now()}`,
|
||||
userId: session.user.id,
|
||||
providerId: 'trello',
|
||||
accountId: trelloUser.id,
|
||||
accessToken: token,
|
||||
scope: 'read,write',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
{ provider: 'Trello', identifier: trelloUser.id }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true })
|
||||
|
||||
@@ -151,8 +151,8 @@ export async function POST(
|
||||
triggerType: 'chat',
|
||||
executionId,
|
||||
requestId,
|
||||
checkRateLimit: false, // Chat bypasses rate limits
|
||||
checkDeployment: true, // Chat requires deployed workflows
|
||||
checkRateLimit: true,
|
||||
checkDeployment: true,
|
||||
loggingSession,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { db } from '@sim/db'
|
||||
import { pausedExecutions, permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
|
||||
import { and, desc, eq, gte, inArray, lte, type SQL, sql } from 'drizzle-orm'
|
||||
import { and, desc, eq, gte, inArray, isNotNull, isNull, lte, or, type SQL, sql } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
@@ -126,13 +126,50 @@ export async function GET(request: NextRequest) {
|
||||
// Build additional conditions for the query
|
||||
let conditions: SQL | undefined
|
||||
|
||||
// Filter by level (supports comma-separated for OR conditions)
|
||||
// Filter by level with support for derived statuses (running, pending)
|
||||
if (params.level && params.level !== 'all') {
|
||||
const levels = params.level.split(',').filter(Boolean)
|
||||
if (levels.length === 1) {
|
||||
conditions = and(conditions, eq(workflowExecutionLogs.level, levels[0]))
|
||||
} else if (levels.length > 1) {
|
||||
conditions = and(conditions, inArray(workflowExecutionLogs.level, levels))
|
||||
const levelConditions: SQL[] = []
|
||||
|
||||
for (const level of levels) {
|
||||
if (level === 'error') {
|
||||
// Direct database field
|
||||
levelConditions.push(eq(workflowExecutionLogs.level, 'error'))
|
||||
} else if (level === 'info') {
|
||||
// Completed info logs only (not running, not pending)
|
||||
const condition = and(
|
||||
eq(workflowExecutionLogs.level, 'info'),
|
||||
isNotNull(workflowExecutionLogs.endedAt)
|
||||
)
|
||||
if (condition) levelConditions.push(condition)
|
||||
} else if (level === 'running') {
|
||||
// Running logs: info level with no endedAt
|
||||
const condition = and(
|
||||
eq(workflowExecutionLogs.level, 'info'),
|
||||
isNull(workflowExecutionLogs.endedAt)
|
||||
)
|
||||
if (condition) levelConditions.push(condition)
|
||||
} else if (level === 'pending') {
|
||||
// Pending logs: info level with pause status indicators
|
||||
const condition = and(
|
||||
eq(workflowExecutionLogs.level, 'info'),
|
||||
or(
|
||||
sql`(${pausedExecutions.totalPauseCount} > 0 AND ${pausedExecutions.resumedCount} < ${pausedExecutions.totalPauseCount})`,
|
||||
and(
|
||||
isNotNull(pausedExecutions.status),
|
||||
sql`${pausedExecutions.status} != 'fully_resumed'`
|
||||
)
|
||||
)
|
||||
)
|
||||
if (condition) levelConditions.push(condition)
|
||||
}
|
||||
}
|
||||
|
||||
if (levelConditions.length > 0) {
|
||||
conditions = and(
|
||||
conditions,
|
||||
levelConditions.length === 1 ? levelConditions[0] : or(...levelConditions)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ const logger = createLogger('McpToolExecutionAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
// Type definitions for improved type safety
|
||||
interface SchemaProperty {
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array'
|
||||
description?: string
|
||||
@@ -31,9 +30,6 @@ interface ToolExecutionResult {
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to safely check if a schema property has a type field
|
||||
*/
|
||||
function hasType(prop: unknown): prop is SchemaProperty {
|
||||
return typeof prop === 'object' && prop !== null && 'type' in prop
|
||||
}
|
||||
@@ -57,7 +53,8 @@ export const POST = withMcpAuth('read')(
|
||||
userId: userId,
|
||||
})
|
||||
|
||||
const { serverId, toolName, arguments: args } = body
|
||||
const { serverId, toolName, arguments: rawArgs } = body
|
||||
const args = rawArgs || {}
|
||||
|
||||
const serverIdValidation = validateStringParam(serverId, 'serverId')
|
||||
if (!serverIdValidation.isValid) {
|
||||
@@ -75,22 +72,31 @@ export const POST = withMcpAuth('read')(
|
||||
`[${requestId}] Executing tool ${toolName} on server ${serverId} for user ${userId} in workspace ${workspaceId}`
|
||||
)
|
||||
|
||||
let tool = null
|
||||
let tool: McpTool | null = null
|
||||
try {
|
||||
const tools = await mcpService.discoverServerTools(userId, serverId, workspaceId)
|
||||
tool = tools.find((t) => t.name === toolName)
|
||||
if (body.toolSchema) {
|
||||
tool = {
|
||||
name: toolName,
|
||||
inputSchema: body.toolSchema,
|
||||
serverId: serverId,
|
||||
serverName: 'provided-schema',
|
||||
} as McpTool
|
||||
logger.debug(`[${requestId}] Using provided schema for ${toolName}, skipping discovery`)
|
||||
} else {
|
||||
const tools = await mcpService.discoverServerTools(userId, serverId, workspaceId)
|
||||
tool = tools.find((t) => t.name === toolName) ?? null
|
||||
|
||||
if (!tool) {
|
||||
return createMcpErrorResponse(
|
||||
new Error(
|
||||
`Tool ${toolName} not found on server ${serverId}. Available tools: ${tools.map((t) => t.name).join(', ')}`
|
||||
),
|
||||
'Tool not found',
|
||||
404
|
||||
)
|
||||
if (!tool) {
|
||||
return createMcpErrorResponse(
|
||||
new Error(
|
||||
`Tool ${toolName} not found on server ${serverId}. Available tools: ${tools.map((t) => t.name).join(', ')}`
|
||||
),
|
||||
'Tool not found',
|
||||
404
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Cast arguments to their expected types based on tool schema
|
||||
if (tool.inputSchema?.properties) {
|
||||
for (const [paramName, paramSchema] of Object.entries(tool.inputSchema.properties)) {
|
||||
const schema = paramSchema as any
|
||||
@@ -100,7 +106,6 @@ export const POST = withMcpAuth('read')(
|
||||
continue
|
||||
}
|
||||
|
||||
// Cast numbers
|
||||
if (
|
||||
(schema.type === 'number' || schema.type === 'integer') &&
|
||||
typeof value === 'string'
|
||||
@@ -110,42 +115,33 @@ export const POST = withMcpAuth('read')(
|
||||
if (!Number.isNaN(numValue)) {
|
||||
args[paramName] = numValue
|
||||
}
|
||||
}
|
||||
// Cast booleans
|
||||
else if (schema.type === 'boolean' && typeof value === 'string') {
|
||||
} else if (schema.type === 'boolean' && typeof value === 'string') {
|
||||
if (value.toLowerCase() === 'true') {
|
||||
args[paramName] = true
|
||||
} else if (value.toLowerCase() === 'false') {
|
||||
args[paramName] = false
|
||||
}
|
||||
}
|
||||
// Cast arrays
|
||||
else if (schema.type === 'array' && typeof value === 'string') {
|
||||
} else if (schema.type === 'array' && typeof value === 'string') {
|
||||
const stringValue = value.trim()
|
||||
if (stringValue) {
|
||||
try {
|
||||
// Try to parse as JSON first (handles ["item1", "item2"])
|
||||
const parsed = JSON.parse(stringValue)
|
||||
if (Array.isArray(parsed)) {
|
||||
args[paramName] = parsed
|
||||
} else {
|
||||
// JSON parsed but not an array, wrap in array
|
||||
args[paramName] = [parsed]
|
||||
}
|
||||
} catch (error) {
|
||||
// JSON parsing failed - treat as comma-separated if contains commas, otherwise single item
|
||||
} catch {
|
||||
if (stringValue.includes(',')) {
|
||||
args[paramName] = stringValue
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter((item) => item)
|
||||
} else {
|
||||
// Single item - wrap in array since schema expects array
|
||||
args[paramName] = [stringValue]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Empty string becomes empty array
|
||||
args[paramName] = []
|
||||
}
|
||||
}
|
||||
@@ -172,7 +168,7 @@ export const POST = withMcpAuth('read')(
|
||||
|
||||
const toolCall: McpToolCall = {
|
||||
name: toolName,
|
||||
arguments: args || {},
|
||||
arguments: args,
|
||||
}
|
||||
|
||||
const result = await Promise.race([
|
||||
@@ -197,7 +193,6 @@ export const POST = withMcpAuth('read')(
|
||||
}
|
||||
logger.info(`[${requestId}] Successfully executed tool ${toolName} on server ${serverId}`)
|
||||
|
||||
// Track MCP tool execution
|
||||
try {
|
||||
const { trackPlatformEvent } = await import('@/lib/core/telemetry')
|
||||
trackPlatformEvent('platform.mcp.tool_executed', {
|
||||
@@ -206,8 +201,8 @@ export const POST = withMcpAuth('read')(
|
||||
'mcp.execution_status': 'success',
|
||||
'workspace.id': workspaceId,
|
||||
})
|
||||
} catch (_e) {
|
||||
// Silently fail
|
||||
} catch {
|
||||
// Telemetry failure is non-critical
|
||||
}
|
||||
|
||||
return createMcpSuccessResponse(transformedResult)
|
||||
@@ -220,12 +215,9 @@ export const POST = withMcpAuth('read')(
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Validate tool arguments against schema
|
||||
*/
|
||||
function validateToolArguments(tool: McpTool, args: Record<string, unknown>): string | null {
|
||||
if (!tool.inputSchema) {
|
||||
return null // No schema to validate against
|
||||
return null
|
||||
}
|
||||
|
||||
const schema = tool.inputSchema
|
||||
@@ -270,9 +262,6 @@ function validateToolArguments(tool: McpTool, args: Record<string, unknown>): st
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform MCP tool result to platform format
|
||||
*/
|
||||
function transformToolResult(result: McpToolResult): ToolExecutionResult {
|
||||
if (result.isError) {
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { db } from '@sim/db'
|
||||
import { member, subscription } from '@sim/db/schema'
|
||||
import { member, organization, subscription } from '@sim/db/schema'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { getPlanPricing } from '@/lib/billing/core/billing'
|
||||
import { requireStripeClient } from '@/lib/billing/stripe-client'
|
||||
import { isBillingEnabled } from '@/lib/core/config/environment'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
@@ -172,6 +173,39 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||
})
|
||||
.where(eq(subscription.id, orgSubscription.id))
|
||||
|
||||
// Update orgUsageLimit to reflect new seat count (seats × basePrice as minimum)
|
||||
const { basePrice } = getPlanPricing('team')
|
||||
const newMinimumLimit = newSeatCount * basePrice
|
||||
|
||||
const orgData = await db
|
||||
.select({ orgUsageLimit: organization.orgUsageLimit })
|
||||
.from(organization)
|
||||
.where(eq(organization.id, organizationId))
|
||||
.limit(1)
|
||||
|
||||
const currentOrgLimit =
|
||||
orgData.length > 0 && orgData[0].orgUsageLimit
|
||||
? Number.parseFloat(orgData[0].orgUsageLimit)
|
||||
: 0
|
||||
|
||||
// Update if new minimum is higher than current limit
|
||||
if (newMinimumLimit > currentOrgLimit) {
|
||||
await db
|
||||
.update(organization)
|
||||
.set({
|
||||
orgUsageLimit: newMinimumLimit.toFixed(2),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(organization.id, organizationId))
|
||||
|
||||
logger.info('Updated organization usage limit for seat change', {
|
||||
organizationId,
|
||||
newSeatCount,
|
||||
newMinimumLimit,
|
||||
previousLimit: currentOrgLimit,
|
||||
})
|
||||
}
|
||||
|
||||
logger.info('Successfully updated seat count', {
|
||||
organizationId,
|
||||
stripeSubscriptionId: orgSubscription.stripeSubscriptionId,
|
||||
|
||||
@@ -4,9 +4,9 @@ import { checkServerSideUsageLimits } from '@/lib/billing'
|
||||
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
|
||||
import { getEffectiveCurrentPeriodCost } from '@/lib/billing/core/usage'
|
||||
import { getUserStorageLimit, getUserStorageUsage } from '@/lib/billing/storage'
|
||||
import { RateLimiter } from '@/lib/core/rate-limiter'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { createErrorResponse } from '@/app/api/workflows/utils'
|
||||
import { RateLimiter } from '@/services/queue'
|
||||
|
||||
const logger = createLogger('UsageLimitsAPI')
|
||||
|
||||
@@ -18,7 +18,6 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
const authenticatedUserId = auth.userId
|
||||
|
||||
// Rate limit info (sync + async), mirroring /users/me/rate-limit
|
||||
const userSubscription = await getHighestPrioritySubscription(authenticatedUserId)
|
||||
const rateLimiter = new RateLimiter()
|
||||
const triggerType = auth.authType === 'api_key' ? 'api' : 'manual'
|
||||
@@ -37,7 +36,6 @@ export async function GET(request: NextRequest) {
|
||||
),
|
||||
])
|
||||
|
||||
// Usage summary (current period cost + limit + plan)
|
||||
const [usageCheck, effectiveCost, storageUsage, storageLimit] = await Promise.all([
|
||||
checkServerSideUsageLimits(authenticatedUserId),
|
||||
getEffectiveCurrentPeriodCost(authenticatedUserId),
|
||||
@@ -52,13 +50,15 @@ export async function GET(request: NextRequest) {
|
||||
rateLimit: {
|
||||
sync: {
|
||||
isLimited: syncStatus.remaining === 0,
|
||||
limit: syncStatus.limit,
|
||||
requestsPerMinute: syncStatus.requestsPerMinute,
|
||||
maxBurst: syncStatus.maxBurst,
|
||||
remaining: syncStatus.remaining,
|
||||
resetAt: syncStatus.resetAt,
|
||||
},
|
||||
async: {
|
||||
isLimited: asyncStatus.remaining === 0,
|
||||
limit: asyncStatus.limit,
|
||||
requestsPerMinute: asyncStatus.requestsPerMinute,
|
||||
maxBurst: asyncStatus.maxBurst,
|
||||
remaining: asyncStatus.remaining,
|
||||
resetAt: asyncStatus.resetAt,
|
||||
},
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { checkServerSideUsageLimits } from '@/lib/billing'
|
||||
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
|
||||
import { getEffectiveCurrentPeriodCost } from '@/lib/billing/core/usage'
|
||||
import { RateLimiter } from '@/services/queue'
|
||||
import { RateLimiter } from '@/lib/core/rate-limiter'
|
||||
|
||||
export interface UserLimits {
|
||||
workflowExecutionRateLimit: {
|
||||
sync: {
|
||||
limit: number
|
||||
requestsPerMinute: number
|
||||
maxBurst: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
async: {
|
||||
limit: number
|
||||
requestsPerMinute: number
|
||||
maxBurst: number
|
||||
remaining: number
|
||||
resetAt: string
|
||||
}
|
||||
@@ -40,12 +42,14 @@ export async function getUserLimits(userId: string): Promise<UserLimits> {
|
||||
return {
|
||||
workflowExecutionRateLimit: {
|
||||
sync: {
|
||||
limit: syncStatus.limit,
|
||||
requestsPerMinute: syncStatus.requestsPerMinute,
|
||||
maxBurst: syncStatus.maxBurst,
|
||||
remaining: syncStatus.remaining,
|
||||
resetAt: syncStatus.resetAt.toISOString(),
|
||||
},
|
||||
async: {
|
||||
limit: asyncStatus.limit,
|
||||
requestsPerMinute: asyncStatus.requestsPerMinute,
|
||||
maxBurst: asyncStatus.maxBurst,
|
||||
remaining: asyncStatus.remaining,
|
||||
resetAt: asyncStatus.resetAt.toISOString(),
|
||||
},
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
|
||||
import { RateLimiter } from '@/lib/core/rate-limiter'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { authenticateV1Request } from '@/app/api/v1/auth'
|
||||
import { RateLimiter } from '@/services/queue/RateLimiter'
|
||||
|
||||
const logger = createLogger('V1Middleware')
|
||||
const rateLimiter = new RateLimiter()
|
||||
@@ -12,6 +12,7 @@ export interface RateLimitResult {
|
||||
remaining: number
|
||||
resetAt: Date
|
||||
limit: number
|
||||
retryAfterMs?: number
|
||||
userId?: string
|
||||
error?: string
|
||||
}
|
||||
@@ -26,7 +27,7 @@ export async function checkRateLimit(
|
||||
return {
|
||||
allowed: false,
|
||||
remaining: 0,
|
||||
limit: 10, // Default to free tier limit
|
||||
limit: 10,
|
||||
resetAt: new Date(),
|
||||
error: auth.error,
|
||||
}
|
||||
@@ -35,12 +36,11 @@ export async function checkRateLimit(
|
||||
const userId = auth.userId!
|
||||
const subscription = await getHighestPrioritySubscription(userId)
|
||||
|
||||
// Use api-endpoint trigger type for external API rate limiting
|
||||
const result = await rateLimiter.checkRateLimitWithSubscription(
|
||||
userId,
|
||||
subscription,
|
||||
'api-endpoint',
|
||||
false // Not relevant for api-endpoint trigger type
|
||||
false
|
||||
)
|
||||
|
||||
if (!result.allowed) {
|
||||
@@ -51,7 +51,6 @@ export async function checkRateLimit(
|
||||
})
|
||||
}
|
||||
|
||||
// Get the actual rate limit for this user's plan
|
||||
const rateLimitStatus = await rateLimiter.getRateLimitStatusWithSubscription(
|
||||
userId,
|
||||
subscription,
|
||||
@@ -60,8 +59,11 @@ export async function checkRateLimit(
|
||||
)
|
||||
|
||||
return {
|
||||
...result,
|
||||
limit: rateLimitStatus.limit,
|
||||
allowed: result.allowed,
|
||||
remaining: result.remaining,
|
||||
resetAt: result.resetAt,
|
||||
limit: rateLimitStatus.requestsPerMinute,
|
||||
retryAfterMs: result.retryAfterMs,
|
||||
userId,
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -88,6 +90,10 @@ export function createRateLimitResponse(result: RateLimitResult): NextResponse {
|
||||
}
|
||||
|
||||
if (!result.allowed) {
|
||||
const retryAfterSeconds = result.retryAfterMs
|
||||
? Math.ceil(result.retryAfterMs / 1000)
|
||||
: Math.ceil((result.resetAt.getTime() - Date.now()) / 1000)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Rate limit exceeded',
|
||||
@@ -98,7 +104,7 @@ export function createRateLimitResponse(result: RateLimitResult): NextResponse {
|
||||
status: 429,
|
||||
headers: {
|
||||
...headers,
|
||||
'Retry-After': Math.ceil((result.resetAt.getTime() - Date.now()) / 1000).toString(),
|
||||
'Retry-After': retryAfterSeconds.toString(),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
66
apps/sim/app/api/webhooks/poll/rss/route.ts
Normal file
66
apps/sim/app/api/webhooks/poll/rss/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { nanoid } from 'nanoid'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { verifyCronAuth } from '@/lib/auth/internal'
|
||||
import { acquireLock, releaseLock } from '@/lib/core/config/redis'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { pollRssWebhooks } from '@/lib/webhooks/rss-polling-service'
|
||||
|
||||
const logger = createLogger('RssPollingAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const maxDuration = 180 // Allow up to 3 minutes for polling to complete
|
||||
|
||||
const LOCK_KEY = 'rss-polling-lock'
|
||||
const LOCK_TTL_SECONDS = 180 // Same as maxDuration (3 min)
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const requestId = nanoid()
|
||||
logger.info(`RSS webhook polling triggered (${requestId})`)
|
||||
|
||||
let lockValue: string | undefined
|
||||
|
||||
try {
|
||||
const authError = verifyCronAuth(request, 'RSS webhook polling')
|
||||
if (authError) {
|
||||
return authError
|
||||
}
|
||||
|
||||
lockValue = requestId
|
||||
const locked = await acquireLock(LOCK_KEY, lockValue, LOCK_TTL_SECONDS)
|
||||
|
||||
if (!locked) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: 'Polling already in progress – skipped',
|
||||
requestId,
|
||||
status: 'skip',
|
||||
},
|
||||
{ status: 202 }
|
||||
)
|
||||
}
|
||||
|
||||
const results = await pollRssWebhooks()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'RSS polling completed',
|
||||
requestId,
|
||||
status: 'completed',
|
||||
...results,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error(`Error during RSS polling (${requestId}):`, error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'RSS polling failed',
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
requestId,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
} finally {
|
||||
await releaseLock(LOCK_KEY).catch(() => {})
|
||||
}
|
||||
}
|
||||
@@ -544,6 +544,43 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
// --- End Outlook specific logic ---
|
||||
|
||||
// --- RSS webhook setup ---
|
||||
if (savedWebhook && provider === 'rss') {
|
||||
logger.info(`[${requestId}] RSS provider detected. Setting up RSS webhook configuration.`)
|
||||
try {
|
||||
const { configureRssPolling } = await import('@/lib/webhooks/utils.server')
|
||||
const success = await configureRssPolling(savedWebhook, requestId)
|
||||
|
||||
if (!success) {
|
||||
logger.error(`[${requestId}] Failed to configure RSS polling, rolling back webhook`)
|
||||
await db.delete(webhook).where(eq(webhook.id, savedWebhook.id))
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to configure RSS polling',
|
||||
details: 'Please try again',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Successfully configured RSS polling`)
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`[${requestId}] Error setting up RSS webhook configuration, rolling back webhook`,
|
||||
err
|
||||
)
|
||||
await db.delete(webhook).where(eq(webhook.id, savedWebhook.id))
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to configure RSS webhook',
|
||||
details: err instanceof Error ? err.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
// --- End RSS specific logic ---
|
||||
|
||||
const status = targetWebhookId ? 200 : 201
|
||||
return NextResponse.json({ webhook: savedWebhook }, { status })
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -140,7 +140,7 @@ vi.mock('@/lib/workspaces/utils', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/services/queue', () => ({
|
||||
vi.mock('@/lib/core/rate-limiter', () => ({
|
||||
RateLimiter: vi.fn().mockImplementation(() => ({
|
||||
checkRateLimit: vi.fn().mockResolvedValue({
|
||||
allowed: true,
|
||||
|
||||
@@ -395,8 +395,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
|
||||
triggerType: loggingTriggerType,
|
||||
executionId,
|
||||
requestId,
|
||||
checkRateLimit: false, // Manual executions bypass rate limits
|
||||
checkDeployment: !shouldUseDraftState, // Check deployment unless using draft
|
||||
checkDeployment: !shouldUseDraftState,
|
||||
loggingSession,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { db } from '@sim/db'
|
||||
import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
|
||||
import { and, eq, gte, inArray, lte } from 'drizzle-orm'
|
||||
import { pausedExecutions, permissions, workflow, workflowExecutionLogs } from '@sim/db/schema'
|
||||
import { and, eq, gte, inArray, isNotNull, isNull, lte, or, type SQL, 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 QueryParamsSchema = z.object({
|
||||
workflowIds: z.string().optional(),
|
||||
folderIds: z.string().optional(),
|
||||
triggers: z.string().optional(),
|
||||
level: z.string().optional(), // Supports comma-separated values: 'error,running'
|
||||
})
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
@@ -84,20 +85,73 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
inArray(workflowExecutionLogs.workflowId, workflowIdList),
|
||||
gte(workflowExecutionLogs.startedAt, start),
|
||||
lte(workflowExecutionLogs.startedAt, end),
|
||||
] as any[]
|
||||
] as SQL[]
|
||||
if (qp.triggers) {
|
||||
const t = qp.triggers.split(',').filter(Boolean)
|
||||
logWhere.push(inArray(workflowExecutionLogs.trigger, t))
|
||||
}
|
||||
|
||||
// Handle level filtering with support for derived statuses and multiple selections
|
||||
if (qp.level && qp.level !== 'all') {
|
||||
const levels = qp.level.split(',').filter(Boolean)
|
||||
const levelConditions: SQL[] = []
|
||||
|
||||
for (const level of levels) {
|
||||
if (level === 'error') {
|
||||
levelConditions.push(eq(workflowExecutionLogs.level, 'error'))
|
||||
} else if (level === 'info') {
|
||||
// Completed info logs only
|
||||
const condition = and(
|
||||
eq(workflowExecutionLogs.level, 'info'),
|
||||
isNotNull(workflowExecutionLogs.endedAt)
|
||||
)
|
||||
if (condition) levelConditions.push(condition)
|
||||
} else if (level === 'running') {
|
||||
// Running logs: info level with no endedAt
|
||||
const condition = and(
|
||||
eq(workflowExecutionLogs.level, 'info'),
|
||||
isNull(workflowExecutionLogs.endedAt)
|
||||
)
|
||||
if (condition) levelConditions.push(condition)
|
||||
} else if (level === 'pending') {
|
||||
// Pending logs: info level with pause status indicators
|
||||
const condition = and(
|
||||
eq(workflowExecutionLogs.level, 'info'),
|
||||
or(
|
||||
sql`(${pausedExecutions.totalPauseCount} > 0 AND ${pausedExecutions.resumedCount} < ${pausedExecutions.totalPauseCount})`,
|
||||
and(
|
||||
isNotNull(pausedExecutions.status),
|
||||
sql`${pausedExecutions.status} != 'fully_resumed'`
|
||||
)
|
||||
)
|
||||
)
|
||||
if (condition) levelConditions.push(condition)
|
||||
}
|
||||
}
|
||||
|
||||
if (levelConditions.length > 0) {
|
||||
const combinedCondition =
|
||||
levelConditions.length === 1 ? levelConditions[0] : or(...levelConditions)
|
||||
if (combinedCondition) logWhere.push(combinedCondition)
|
||||
}
|
||||
}
|
||||
|
||||
const logs = await db
|
||||
.select({
|
||||
workflowId: workflowExecutionLogs.workflowId,
|
||||
level: workflowExecutionLogs.level,
|
||||
startedAt: workflowExecutionLogs.startedAt,
|
||||
endedAt: workflowExecutionLogs.endedAt,
|
||||
totalDurationMs: workflowExecutionLogs.totalDurationMs,
|
||||
pausedTotalPauseCount: pausedExecutions.totalPauseCount,
|
||||
pausedResumedCount: pausedExecutions.resumedCount,
|
||||
pausedStatus: pausedExecutions.status,
|
||||
})
|
||||
.from(workflowExecutionLogs)
|
||||
.leftJoin(
|
||||
pausedExecutions,
|
||||
eq(pausedExecutions.executionId, workflowExecutionLogs.executionId)
|
||||
)
|
||||
.where(and(...logWhere))
|
||||
|
||||
type Bucket = {
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { ArrowDown, Download, Loader2, Music } from 'lucide-react'
|
||||
import { Button } from '@/components/emcn'
|
||||
import { DefaultFileIcon, getDocumentIcon } from '@/components/icons/document-icons'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ChatFile } from '@/app/chat/components/message/message'
|
||||
|
||||
const logger = createLogger('ChatFileDownload')
|
||||
|
||||
interface ChatFileDownloadProps {
|
||||
file: ChatFile
|
||||
}
|
||||
|
||||
interface ChatFileDownloadAllProps {
|
||||
files: ChatFile[]
|
||||
}
|
||||
|
||||
function formatFileSize(bytes: number): string {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return `${Math.round((bytes / k ** i) * 10) / 10} ${sizes[i]}`
|
||||
}
|
||||
|
||||
function isAudioFile(mimeType: string, filename: string): boolean {
|
||||
const audioMimeTypes = [
|
||||
'audio/mpeg',
|
||||
'audio/wav',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/webm',
|
||||
'audio/aac',
|
||||
'audio/flac',
|
||||
]
|
||||
const audioExtensions = ['mp3', 'wav', 'ogg', 'webm', 'aac', 'flac', 'm4a']
|
||||
const extension = filename.split('.').pop()?.toLowerCase()
|
||||
|
||||
return (
|
||||
audioMimeTypes.some((t) => mimeType.includes(t)) ||
|
||||
(extension ? audioExtensions.includes(extension) : false)
|
||||
)
|
||||
}
|
||||
|
||||
function isImageFile(mimeType: string): boolean {
|
||||
return mimeType.startsWith('image/')
|
||||
}
|
||||
|
||||
function getFileUrl(file: ChatFile): string {
|
||||
return `/api/files/serve/${encodeURIComponent(file.key)}?context=${file.context || 'execution'}`
|
||||
}
|
||||
|
||||
async function triggerDownload(url: string, filename: string): Promise<void> {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch file: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = blobUrl
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
|
||||
URL.revokeObjectURL(blobUrl)
|
||||
logger.info(`Downloaded: ${filename}`)
|
||||
}
|
||||
|
||||
export function ChatFileDownload({ file }: ChatFileDownloadProps) {
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (isDownloading) return
|
||||
|
||||
setIsDownloading(true)
|
||||
|
||||
try {
|
||||
logger.info(`Initiating download for file: ${file.name}`)
|
||||
const url = getFileUrl(file)
|
||||
await triggerDownload(url, file.name)
|
||||
} catch (error) {
|
||||
logger.error(`Failed to download file ${file.name}:`, error)
|
||||
if (file.url) {
|
||||
window.open(file.url, '_blank')
|
||||
}
|
||||
} finally {
|
||||
setIsDownloading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const renderIcon = () => {
|
||||
if (isAudioFile(file.type, file.name)) {
|
||||
return <Music className='h-4 w-4 text-purple-500' />
|
||||
}
|
||||
if (isImageFile(file.type)) {
|
||||
const ImageIcon = DefaultFileIcon
|
||||
return <ImageIcon className='h-5 w-5' />
|
||||
}
|
||||
const DocumentIcon = getDocumentIcon(file.type, file.name)
|
||||
return <DocumentIcon className='h-5 w-5' />
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant='default'
|
||||
onClick={handleDownload}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
disabled={isDownloading}
|
||||
className='flex h-auto w-[200px] items-center gap-2 rounded-lg px-3 py-2'
|
||||
>
|
||||
<div className='flex h-8 w-8 flex-shrink-0 items-center justify-center'>{renderIcon()}</div>
|
||||
<div className='min-w-0 flex-1 text-left'>
|
||||
<div className='w-[100px] truncate text-xs'>{file.name}</div>
|
||||
<div className='text-[10px] text-[var(--text-muted)]'>{formatFileSize(file.size)}</div>
|
||||
</div>
|
||||
<div className='flex-shrink-0'>
|
||||
{isDownloading ? (
|
||||
<Loader2 className='h-3.5 w-3.5 animate-spin' />
|
||||
) : (
|
||||
<ArrowDown
|
||||
className={`h-3.5 w-3.5 transition-opacity ${isHovered ? 'opacity-100' : 'opacity-0'}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export function ChatFileDownloadAll({ files }: ChatFileDownloadAllProps) {
|
||||
const [isDownloading, setIsDownloading] = useState(false)
|
||||
|
||||
if (!files || files.length === 0) return null
|
||||
|
||||
const handleDownloadAll = async () => {
|
||||
if (isDownloading) return
|
||||
|
||||
setIsDownloading(true)
|
||||
|
||||
try {
|
||||
logger.info(`Initiating download for ${files.length} files`)
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i]
|
||||
try {
|
||||
const url = getFileUrl(file)
|
||||
await triggerDownload(url, file.name)
|
||||
logger.info(`Downloaded file ${i + 1}/${files.length}: ${file.name}`)
|
||||
|
||||
if (i < files.length - 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 150))
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to download file ${file.name}:`, error)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setIsDownloading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleDownloadAll}
|
||||
disabled={isDownloading}
|
||||
className='text-muted-foreground transition-colors hover:bg-muted disabled:opacity-50'
|
||||
>
|
||||
{isDownloading ? (
|
||||
<Loader2 className='h-3 w-3 animate-spin' strokeWidth={2} />
|
||||
) : (
|
||||
<Download className='h-3 w-3' strokeWidth={2} />
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
import { memo, useMemo, useState } from 'react'
|
||||
import { Check, Copy, File as FileIcon, FileText, Image as ImageIcon } from 'lucide-react'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import {
|
||||
ChatFileDownload,
|
||||
ChatFileDownloadAll,
|
||||
} from '@/app/chat/components/message/components/file-download'
|
||||
import MarkdownRenderer from '@/app/chat/components/message/components/markdown-renderer'
|
||||
|
||||
export interface ChatAttachment {
|
||||
@@ -13,6 +17,16 @@ export interface ChatAttachment {
|
||||
size?: number
|
||||
}
|
||||
|
||||
export interface ChatFile {
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
key: string
|
||||
size: number
|
||||
type: string
|
||||
context?: string
|
||||
}
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string
|
||||
content: string | Record<string, unknown>
|
||||
@@ -21,6 +35,7 @@ export interface ChatMessage {
|
||||
isInitialMessage?: boolean
|
||||
isStreaming?: boolean
|
||||
attachments?: ChatAttachment[]
|
||||
files?: ChatFile[]
|
||||
}
|
||||
|
||||
function EnhancedMarkdownRenderer({ content }: { content: string }) {
|
||||
@@ -177,6 +192,13 @@ export const ClientChatMessage = memo(
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{message.files && message.files.length > 0 && (
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
{message.files.map((file) => (
|
||||
<ChatFileDownload key={file.id} file={file} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{message.type === 'assistant' && !isJsonObject && !message.isInitialMessage && (
|
||||
<div className='flex items-center justify-start space-x-2'>
|
||||
{/* Copy Button - Only show when not streaming */}
|
||||
@@ -207,6 +229,10 @@ export const ClientChatMessage = memo(
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
)}
|
||||
{/* Download All Button - Only show when there are files */}
|
||||
{!message.isStreaming && message.files && (
|
||||
<ChatFileDownloadAll files={message.files} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -221,7 +247,8 @@ export const ClientChatMessage = memo(
|
||||
prevProps.message.id === nextProps.message.id &&
|
||||
prevProps.message.content === nextProps.message.content &&
|
||||
prevProps.message.isStreaming === nextProps.message.isStreaming &&
|
||||
prevProps.message.isInitialMessage === nextProps.message.isInitialMessage
|
||||
prevProps.message.isInitialMessage === nextProps.message.isInitialMessage &&
|
||||
prevProps.message.files?.length === nextProps.message.files?.length
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,12 +1,52 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { isUserFile } from '@/lib/core/utils/display-filters'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ChatMessage } from '@/app/chat/components/message/message'
|
||||
import type { ChatFile, ChatMessage } from '@/app/chat/components/message/message'
|
||||
import { CHAT_ERROR_MESSAGES } from '@/app/chat/constants'
|
||||
|
||||
const logger = createLogger('UseChatStreaming')
|
||||
|
||||
function extractFilesFromData(
|
||||
data: any,
|
||||
files: ChatFile[] = [],
|
||||
seenIds = new Set<string>()
|
||||
): ChatFile[] {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return files
|
||||
}
|
||||
|
||||
if (isUserFile(data)) {
|
||||
if (!seenIds.has(data.id)) {
|
||||
seenIds.add(data.id)
|
||||
files.push({
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
url: data.url,
|
||||
key: data.key,
|
||||
size: data.size,
|
||||
type: data.type,
|
||||
context: data.context,
|
||||
})
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
for (const item of data) {
|
||||
extractFilesFromData(item, files, seenIds)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
for (const value of Object.values(data)) {
|
||||
extractFilesFromData(value, files, seenIds)
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
export interface VoiceSettings {
|
||||
isVoiceEnabled: boolean
|
||||
voiceId: string
|
||||
@@ -185,12 +225,21 @@ export function useChatStreaming() {
|
||||
|
||||
const outputConfigs = streamingOptions?.outputConfigs
|
||||
const formattedOutputs: string[] = []
|
||||
let extractedFiles: ChatFile[] = []
|
||||
|
||||
const formatValue = (value: any): string | null => {
|
||||
if (value === null || value === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (isUserFile(value)) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (Array.isArray(value) && value.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
@@ -235,6 +284,26 @@ export function useChatStreaming() {
|
||||
if (!blockOutputs) continue
|
||||
|
||||
const value = getOutputValue(blockOutputs, config.path)
|
||||
|
||||
if (isUserFile(value)) {
|
||||
extractedFiles.push({
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
url: value.url,
|
||||
key: value.key,
|
||||
size: value.size,
|
||||
type: value.type,
|
||||
context: value.context,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
const nestedFiles = extractFilesFromData(value)
|
||||
if (nestedFiles.length > 0) {
|
||||
extractedFiles = [...extractedFiles, ...nestedFiles]
|
||||
continue
|
||||
}
|
||||
|
||||
const formatted = formatValue(value)
|
||||
if (formatted) {
|
||||
formattedOutputs.push(formatted)
|
||||
@@ -267,7 +336,7 @@ export function useChatStreaming() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalContent) {
|
||||
if (!finalContent && extractedFiles.length === 0) {
|
||||
if (finalData.error) {
|
||||
if (typeof finalData.error === 'string') {
|
||||
finalContent = finalData.error
|
||||
@@ -291,6 +360,7 @@ export function useChatStreaming() {
|
||||
...msg,
|
||||
isStreaming: false,
|
||||
content: finalContent ?? msg.content,
|
||||
files: extractedFiles.length > 0 ? extractedFiles : undefined,
|
||||
}
|
||||
: msg
|
||||
)
|
||||
|
||||
@@ -4,11 +4,41 @@ import { useEffect, useState } from 'react'
|
||||
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { client, useSession } from '@/lib/auth/auth-client'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { getErrorMessage } from '@/app/invite/[id]/utils'
|
||||
import { InviteLayout, InviteStatusCard } from '@/app/invite/components'
|
||||
|
||||
const logger = createLogger('InviteById')
|
||||
|
||||
function getErrorMessage(reason: string): string {
|
||||
switch (reason) {
|
||||
case 'missing-token':
|
||||
return 'The invitation link is invalid or missing a required parameter.'
|
||||
case 'invalid-token':
|
||||
return 'The invitation link is invalid or has already been used.'
|
||||
case 'expired':
|
||||
return 'This invitation has expired. Please ask for a new invitation.'
|
||||
case 'already-processed':
|
||||
return 'This invitation has already been accepted or declined.'
|
||||
case 'email-mismatch':
|
||||
return 'This invitation was sent to a different email address. Please log in with the correct account.'
|
||||
case 'workspace-not-found':
|
||||
return 'The workspace associated with this invitation could not be found.'
|
||||
case 'user-not-found':
|
||||
return 'Your user account could not be found. Please try logging out and logging back in.'
|
||||
case 'already-member':
|
||||
return 'You are already a member of this organization or workspace.'
|
||||
case 'already-in-organization':
|
||||
return 'You are already a member of an organization. Leave your current organization before accepting a new invitation.'
|
||||
case 'invalid-invitation':
|
||||
return 'This invitation is invalid or no longer exists.'
|
||||
case 'missing-invitation-id':
|
||||
return 'The invitation link is missing required information. Please use the original invitation link.'
|
||||
case 'server-error':
|
||||
return 'An unexpected error occurred while processing your invitation. Please try again later.'
|
||||
default:
|
||||
return 'An unknown error occurred while processing your invitation.'
|
||||
}
|
||||
}
|
||||
|
||||
export default function Invite() {
|
||||
const router = useRouter()
|
||||
const params = useParams()
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
export function getErrorMessage(reason: string): string {
|
||||
switch (reason) {
|
||||
case 'missing-token':
|
||||
return 'The invitation link is invalid or missing a required parameter.'
|
||||
case 'invalid-token':
|
||||
return 'The invitation link is invalid or has already been used.'
|
||||
case 'expired':
|
||||
return 'This invitation has expired. Please ask for a new invitation.'
|
||||
case 'already-processed':
|
||||
return 'This invitation has already been accepted or declined.'
|
||||
case 'email-mismatch':
|
||||
return 'This invitation was sent to a different email address. Please log in with the correct account.'
|
||||
case 'workspace-not-found':
|
||||
return 'The workspace associated with this invitation could not be found.'
|
||||
case 'user-not-found':
|
||||
return 'Your user account could not be found. Please try logging out and logging back in.'
|
||||
case 'already-member':
|
||||
return 'You are already a member of this organization or workspace.'
|
||||
case 'already-in-organization':
|
||||
return 'You are already a member of an organization. Leave your current organization before accepting a new invitation.'
|
||||
case 'invalid-invitation':
|
||||
return 'This invitation is invalid or no longer exists.'
|
||||
case 'missing-invitation-id':
|
||||
return 'The invitation link is missing required information. Please use the original invitation link.'
|
||||
case 'server-error':
|
||||
return 'An unexpected error occurred while processing your invitation. Please try again later.'
|
||||
default:
|
||||
return 'An unknown error occurred while processing your invitation.'
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import {
|
||||
ArrowLeft,
|
||||
ChartNoAxesColumn,
|
||||
ChevronDown,
|
||||
Globe,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
import { useParams, useRouter, useSearchParams } from 'next/navigation'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Copy,
|
||||
Popover,
|
||||
@@ -267,13 +267,13 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
}
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
if (isWorkspaceContext) {
|
||||
router.back()
|
||||
} else {
|
||||
router.push('/templates')
|
||||
}
|
||||
}
|
||||
const breadcrumbItems = [
|
||||
{
|
||||
label: 'Templates',
|
||||
href: isWorkspaceContext ? `/workspace/${workspaceId}/templates` : '/templates',
|
||||
},
|
||||
{ label: template?.name || 'Template' },
|
||||
]
|
||||
/**
|
||||
* Intercepts wheel events over the workflow preview so that the page handles scrolling
|
||||
* instead of the underlying canvas. We stop propagation in the capture phase to prevent
|
||||
@@ -542,24 +542,15 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex min-h-screen flex-col', isWorkspaceContext && 'pl-64')}>
|
||||
<div className={cn('flex flex-col', isWorkspaceContext ? 'h-full flex-1' : 'min-h-screen')}>
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[24px] pb-[24px]'>
|
||||
{/* Top bar with back button */}
|
||||
<div className='flex items-center justify-between'>
|
||||
{/* Back button */}
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className='flex items-center gap-[6px] font-medium text-[#ADADAD] text-[14px] transition-colors hover:text-white'
|
||||
>
|
||||
<ArrowLeft className='h-[14px] w-[14px]' />
|
||||
<span>More Templates</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* Breadcrumb navigation */}
|
||||
<Breadcrumb items={breadcrumbItems} />
|
||||
|
||||
{/* Template name and action buttons */}
|
||||
<div className='mt-[24px] flex items-center justify-between'>
|
||||
<h1 className='font-medium text-[18px]'>{template.name}</h1>
|
||||
<div className='mt-[14px] flex items-center justify-between'>
|
||||
<h1 className='font-medium text-[18px] text-[var(--text-primary)]'>{template.name}</h1>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className='flex items-center gap-[8px]'>
|
||||
@@ -706,7 +697,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
|
||||
{/* Template tagline */}
|
||||
{template.details?.tagline && (
|
||||
<p className='mt-[4px] font-medium text-[#888888] text-[14px]'>
|
||||
<p className='mt-[4px] font-medium text-[14px] text-[var(--text-tertiary)]'>
|
||||
{template.details.tagline}
|
||||
</p>
|
||||
)}
|
||||
@@ -718,18 +709,22 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
onClick={handleStarToggle}
|
||||
className={cn(
|
||||
'h-[14px] w-[14px] cursor-pointer transition-colors',
|
||||
template.isStarred ? 'fill-yellow-500 text-yellow-500' : 'text-[#888888]',
|
||||
template.isStarred ? 'fill-yellow-500 text-yellow-500' : 'text-[var(--text-muted)]',
|
||||
starTemplate.isPending && 'opacity-50'
|
||||
)}
|
||||
/>
|
||||
<span className='font-medium text-[#888888] text-[14px]'>{template.stars || 0}</span>
|
||||
<span className='font-medium text-[14px] text-[var(--text-muted)]'>
|
||||
{template.stars || 0}
|
||||
</span>
|
||||
|
||||
{/* Users icon and count */}
|
||||
<ChartNoAxesColumn className='h-[16px] w-[16px] text-[#888888]' />
|
||||
<span className='font-medium text-[#888888] text-[14px]'>{template.views}</span>
|
||||
<ChartNoAxesColumn className='h-[16px] w-[16px] text-[var(--text-muted)]' />
|
||||
<span className='font-medium text-[14px] text-[var(--text-muted)]'>
|
||||
{template.views}
|
||||
</span>
|
||||
|
||||
{/* Vertical divider */}
|
||||
<div className='mx-[4px] mb-[-1.5px] h-[18px] w-[1.25px] rounded-full bg-[#3A3A3A]' />
|
||||
<div className='mx-[4px] mb-[-1.5px] h-[18px] w-[1.25px] rounded-full bg-[var(--border)]' />
|
||||
|
||||
{/* Creator profile pic */}
|
||||
{template.creator?.profileImageUrl ? (
|
||||
@@ -741,13 +736,13 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-full bg-[#4A4A4A]'>
|
||||
<User className='h-[14px] w-[14px] text-[#888888]' />
|
||||
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-full bg-[var(--surface-elevated)]'>
|
||||
<User className='h-[14px] w-[14px] text-[var(--text-muted)]' />
|
||||
</div>
|
||||
)}
|
||||
{/* Creator name */}
|
||||
<div className='flex items-center gap-[4px]'>
|
||||
<span className='font-medium text-[#8B8B8B] text-[14px]'>
|
||||
<span className='font-medium text-[14px] text-[var(--text-muted)]'>
|
||||
{template.creator?.name || 'Unknown'}
|
||||
</span>
|
||||
{template.creator?.verified && <VerifiedBadge size='md' />}
|
||||
@@ -757,7 +752,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
{/* Credentials needed */}
|
||||
{Array.isArray(template.requiredCredentials) &&
|
||||
template.requiredCredentials.length > 0 && (
|
||||
<p className='mt-[12px] font-medium text-[#888888] text-[12px]'>
|
||||
<p className='mt-[12px] font-medium text-[12px] text-[var(--text-muted)]'>
|
||||
Credentials needed:{' '}
|
||||
{template.requiredCredentials
|
||||
.map((cred: CredentialRequirement) => {
|
||||
@@ -783,7 +778,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
{/* Last updated overlay */}
|
||||
{template.updatedAt && (
|
||||
<div className='pointer-events-none absolute right-[12px] bottom-[12px] rounded-[4px] bg-[var(--bg)]/80 px-[8px] py-[4px] backdrop-blur-sm'>
|
||||
<span className='font-medium text-[#8B8B8B] text-[12px]'>
|
||||
<span className='font-medium text-[12px] text-[var(--text-muted)]'>
|
||||
Last updated{' '}
|
||||
{formatDistanceToNow(new Date(template.updatedAt), {
|
||||
addSuffix: true,
|
||||
@@ -910,8 +905,8 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex h-[48px] w-[48px] flex-shrink-0 items-center justify-center rounded-full bg-[#4A4A4A]'>
|
||||
<User className='h-[24px] w-[24px] text-[#888888]' />
|
||||
<div className='flex h-[48px] w-[48px] flex-shrink-0 items-center justify-center rounded-full bg-[var(--surface-elevated)]'>
|
||||
<User className='h-[24px] w-[24px] text-[var(--text-muted)]' />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -932,7 +927,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
href={template.creator.details.websiteUrl}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='flex items-center text-[#888888] transition-colors hover:text-[var(--text-primary)]'
|
||||
className='flex items-center text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)]'
|
||||
aria-label='Website'
|
||||
>
|
||||
<Globe className='h-[14px] w-[14px]' />
|
||||
@@ -943,7 +938,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
href={template.creator.details.xUrl}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='flex items-center text-[#888888] transition-colors hover:text-[var(--text-primary)]'
|
||||
className='flex items-center text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)]'
|
||||
aria-label='X (Twitter)'
|
||||
>
|
||||
<svg
|
||||
@@ -960,7 +955,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
href={template.creator.details.linkedinUrl}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='flex items-center text-[#888888] transition-colors hover:text-[var(--text-primary)]'
|
||||
className='flex items-center text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)]'
|
||||
aria-label='LinkedIn'
|
||||
>
|
||||
<Linkedin className='h-[14px] w-[14px]' />
|
||||
@@ -969,7 +964,7 @@ export default function TemplateDetails({ isWorkspaceContext = false }: Template
|
||||
{template.creator.details?.contactEmail && (
|
||||
<a
|
||||
href={`mailto:${template.creator.details.contactEmail}`}
|
||||
className='flex items-center text-[#888888] transition-colors hover:text-[var(--text-primary)]'
|
||||
className='flex items-center text-[var(--text-muted)] transition-colors hover:text-[var(--text-primary)]'
|
||||
aria-label='Email'
|
||||
>
|
||||
<Mail className='h-[14px] w-[14px]' />
|
||||
|
||||
@@ -199,7 +199,7 @@ function TemplateCardInner({
|
||||
>
|
||||
<div
|
||||
ref={previewRef}
|
||||
className='pointer-events-none h-[180px] w-full overflow-hidden rounded-[6px]'
|
||||
className='pointer-events-none h-[180px] w-full cursor-pointer overflow-hidden rounded-[6px]'
|
||||
>
|
||||
{normalizedState && isInView ? (
|
||||
<WorkflowPreview
|
||||
|
||||
@@ -135,15 +135,15 @@ export default function Templates({
|
||||
return (
|
||||
<div className='flex h-[100vh] flex-col'>
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[24px] pb-[24px]'>
|
||||
<div className='flex flex-1 flex-col overflow-auto px-[24px] pt-[28px] pb-[24px]'>
|
||||
<div>
|
||||
<div className='flex items-start gap-[12px]'>
|
||||
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#7A5F11] bg-[#514215]'>
|
||||
<Layout className='h-[14px] w-[14px] text-[#FBBC04]' />
|
||||
<div className='flex h-[26px] w-[26px] items-center justify-center rounded-[6px] border border-[#1E3A5A] bg-[#0F2A3D]'>
|
||||
<Layout className='h-[14px] w-[14px] text-[#60A5FA]' />
|
||||
</div>
|
||||
<h1 className='font-medium text-[18px]'>Templates</h1>
|
||||
</div>
|
||||
<p className='mt-[10px] font-base text-[#888888] text-[14px]'>
|
||||
<p className='mt-[10px] text-[14px] text-[var(--text-tertiary)]'>
|
||||
Grab a template and start building, or make one from scratch.
|
||||
</p>
|
||||
</div>
|
||||
@@ -178,15 +178,13 @@ export default function Templates({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-[24px] h-[1px] w-full border-[var(--border)] border-t' />
|
||||
|
||||
<div className='mt-[24px] grid grid-cols-1 gap-x-[20px] gap-y-[40px] md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'>
|
||||
{loading ? (
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
<TemplateCardSkeleton key={`skeleton-${index}`} />
|
||||
))
|
||||
) : filteredTemplates.length === 0 ? (
|
||||
<div className='col-span-full flex h-64 items-center justify-center rounded-lg border border-muted-foreground/25 border-dashed bg-muted/20'>
|
||||
<div className='col-span-full flex h-64 items-center justify-center rounded-lg border border-muted-foreground/25 bg-muted/20'>
|
||||
<div className='text-center'>
|
||||
<p className='font-medium text-muted-foreground text-sm'>{emptyState.title}</p>
|
||||
<p className='mt-1 text-muted-foreground/70 text-xs'>{emptyState.description}</p>
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { AlertCircle, Loader2 } from 'lucide-react'
|
||||
import { Button, Textarea } from '@/components/emcn'
|
||||
import {
|
||||
Button,
|
||||
Label,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
} from '@/components/emcn/components/modal/modal'
|
||||
import { Label } from '@/components/ui/label'
|
||||
Textarea,
|
||||
} from '@/components/emcn'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ChunkData, DocumentData } from '@/stores/knowledge/store'
|
||||
|
||||
@@ -119,24 +120,12 @@ export function CreateChunkModal({
|
||||
return (
|
||||
<>
|
||||
<Modal open={open} onOpenChange={handleCloseAttempt}>
|
||||
<ModalContent className='h-[74vh] sm:max-w-[600px]'>
|
||||
<ModalContent size='lg'>
|
||||
<ModalHeader>Create Chunk</ModalHeader>
|
||||
|
||||
<form className='flex min-h-0 flex-1 flex-col'>
|
||||
<ModalBody>
|
||||
<div className='space-y-[12px]'>
|
||||
{/* Document Info Section */}
|
||||
<div className='flex items-center gap-3 rounded-lg border p-4'>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<p className='font-medium text-[var(--text-primary)] text-sm'>
|
||||
{document?.filename || 'Unknown Document'}
|
||||
</p>
|
||||
<p className='text-[var(--text-tertiary)] text-xs'>
|
||||
Adding chunk to this document
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<ModalBody className='!pb-[16px]'>
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
{/* Error Display */}
|
||||
{error && (
|
||||
<div className='flex items-center gap-2 rounded-md border border-[var(--text-error)]/50 bg-[var(--text-error)]/10 p-3'>
|
||||
@@ -146,22 +135,15 @@ export function CreateChunkModal({
|
||||
)}
|
||||
|
||||
{/* Content Input Section */}
|
||||
<div className='space-y-[8px]'>
|
||||
<Label
|
||||
htmlFor='content'
|
||||
className='mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'
|
||||
>
|
||||
Chunk Content
|
||||
</Label>
|
||||
<Textarea
|
||||
id='content'
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder='Enter the content for this chunk...'
|
||||
rows={10}
|
||||
disabled={isCreating}
|
||||
/>
|
||||
</div>
|
||||
<Label htmlFor='content'>Chunk</Label>
|
||||
<Textarea
|
||||
id='content'
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder='Enter the content for this chunk...'
|
||||
rows={12}
|
||||
disabled={isCreating}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
@@ -196,7 +178,7 @@ export function CreateChunkModal({
|
||||
|
||||
{/* Unsaved Changes Alert */}
|
||||
<Modal open={showUnsavedChangesAlert} onOpenChange={setShowUnsavedChangesAlert}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Discard Changes</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn'
|
||||
import { Trash } from '@/components/emcn/icons/trash'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type { ChunkData } from '@/stores/knowledge/store'
|
||||
|
||||
@@ -68,7 +66,7 @@ export function DeleteChunkModal({
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onOpenChange={onClose}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Delete Chunk</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
@@ -86,17 +84,7 @@ export function DeleteChunkModal({
|
||||
disabled={isDeleting}
|
||||
className='!bg-[var(--text-error)] !text-white hover:!bg-[var(--text-error)]/90'
|
||||
>
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash className='mr-2 h-4 w-4' />
|
||||
Delete
|
||||
</>
|
||||
)}
|
||||
{isDeleting ? <>Deleting...</> : <>Delete</>}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { Search } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Button } from '@/components/emcn'
|
||||
import {
|
||||
ChunkTableSkeleton,
|
||||
KnowledgeHeader,
|
||||
} from '@/app/workspace/[workspaceId]/knowledge/components'
|
||||
|
||||
interface DocumentLoadingProps {
|
||||
knowledgeBaseId: string
|
||||
knowledgeBaseName: string
|
||||
documentName: string
|
||||
}
|
||||
|
||||
export function DocumentLoading({
|
||||
knowledgeBaseId,
|
||||
knowledgeBaseName,
|
||||
documentName,
|
||||
}: DocumentLoadingProps) {
|
||||
const params = useParams()
|
||||
const workspaceId = params?.workspaceId as string
|
||||
|
||||
const breadcrumbs = [
|
||||
{
|
||||
id: 'knowledge-root',
|
||||
label: 'Knowledge',
|
||||
href: `/workspace/${workspaceId}/knowledge`,
|
||||
},
|
||||
{
|
||||
id: `knowledge-base-${knowledgeBaseId}`,
|
||||
label: knowledgeBaseName,
|
||||
href: `/workspace/${workspaceId}/knowledge/${knowledgeBaseId}`,
|
||||
},
|
||||
{
|
||||
id: `document-${knowledgeBaseId}-${documentName}`,
|
||||
label: documentName,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className='flex h-[100vh] flex-col pl-64'>
|
||||
{/* Header with Breadcrumbs */}
|
||||
<KnowledgeHeader breadcrumbs={breadcrumbs} />
|
||||
|
||||
<div className='flex flex-1 overflow-hidden'>
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 overflow-auto'>
|
||||
<div className='px-6 pb-6'>
|
||||
{/* Search Section */}
|
||||
<div className='mb-4 flex items-center justify-between pt-1'>
|
||||
<div className='relative max-w-md'>
|
||||
<div className='relative flex items-center'>
|
||||
<Search className='-translate-y-1/2 pointer-events-none absolute top-1/2 left-3 h-[18px] w-[18px] transform text-muted-foreground' />
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Search chunks...'
|
||||
disabled
|
||||
className='h-10 w-full rounded-md border bg-background px-9 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button disabled variant='primary' className='flex items-center gap-1'>
|
||||
<div className='h-3.5 w-3.5 animate-pulse rounded bg-primary-foreground/30' />
|
||||
<span>Create Chunk</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Table container */}
|
||||
<ChunkTableSkeleton isSidebarCollapsed={false} rowCount={8} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
Combobox,
|
||||
Input,
|
||||
Label,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
Trash,
|
||||
} from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { MAX_TAG_SLOTS, TAG_SLOTS, type TagSlot } from '@/lib/knowledge/constants'
|
||||
import type { DocumentTag } from '@/lib/knowledge/tags/types'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import {
|
||||
type TagDefinition,
|
||||
useKnowledgeBaseTagDefinitions,
|
||||
} from '@/hooks/use-knowledge-base-tag-definitions'
|
||||
import { useNextAvailableSlot } from '@/hooks/use-next-available-slot'
|
||||
import { type TagDefinitionInput, useTagDefinitions } from '@/hooks/use-tag-definitions'
|
||||
import { type DocumentData, useKnowledgeStore } from '@/stores/knowledge/store'
|
||||
|
||||
const logger = createLogger('DocumentTagsModal')
|
||||
|
||||
interface DocumentTagsModalProps {
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
knowledgeBaseId: string
|
||||
documentId: string
|
||||
documentData: DocumentData | null
|
||||
onDocumentUpdate?: (updates: Record<string, string>) => void
|
||||
}
|
||||
|
||||
export function DocumentTagsModal({
|
||||
open,
|
||||
onOpenChange,
|
||||
knowledgeBaseId,
|
||||
documentId,
|
||||
documentData,
|
||||
onDocumentUpdate,
|
||||
}: DocumentTagsModalProps) {
|
||||
const { updateDocument: updateDocumentInStore } = useKnowledgeStore()
|
||||
|
||||
const documentTagHook = useTagDefinitions(knowledgeBaseId, documentId)
|
||||
const kbTagHook = useKnowledgeBaseTagDefinitions(knowledgeBaseId)
|
||||
const { getNextAvailableSlot: getServerNextSlot } = useNextAvailableSlot(knowledgeBaseId)
|
||||
|
||||
const { saveTagDefinitions, tagDefinitions, fetchTagDefinitions } = documentTagHook
|
||||
const { tagDefinitions: kbTagDefinitions, fetchTagDefinitions: refreshTagDefinitions } = kbTagHook
|
||||
|
||||
const [documentTags, setDocumentTags] = useState<DocumentTag[]>([])
|
||||
const [editingTagIndex, setEditingTagIndex] = useState<number | null>(null)
|
||||
const [isCreatingTag, setIsCreatingTag] = useState(false)
|
||||
const [isSavingTag, setIsSavingTag] = useState(false)
|
||||
const [editTagForm, setEditTagForm] = useState({
|
||||
displayName: '',
|
||||
fieldType: 'text',
|
||||
value: '',
|
||||
})
|
||||
|
||||
const buildDocumentTags = useCallback((docData: DocumentData, definitions: TagDefinition[]) => {
|
||||
const tags: DocumentTag[] = []
|
||||
|
||||
TAG_SLOTS.forEach((slot) => {
|
||||
const value = docData[slot] as string | null | undefined
|
||||
const definition = definitions.find((def) => def.tagSlot === slot)
|
||||
|
||||
if (value?.trim() && definition) {
|
||||
tags.push({
|
||||
slot,
|
||||
displayName: definition.displayName,
|
||||
fieldType: definition.fieldType,
|
||||
value: value.trim(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return tags
|
||||
}, [])
|
||||
|
||||
const handleTagsChange = useCallback((newTags: DocumentTag[]) => {
|
||||
setDocumentTags(newTags)
|
||||
}, [])
|
||||
|
||||
const handleSaveDocumentTags = useCallback(
|
||||
async (tagsToSave: DocumentTag[]) => {
|
||||
if (!documentData) return
|
||||
|
||||
try {
|
||||
const tagData: Record<string, string> = {}
|
||||
|
||||
TAG_SLOTS.forEach((slot) => {
|
||||
tagData[slot] = ''
|
||||
})
|
||||
|
||||
tagsToSave.forEach((tag) => {
|
||||
if (tag.value.trim()) {
|
||||
tagData[tag.slot] = tag.value.trim()
|
||||
}
|
||||
})
|
||||
|
||||
const response = await fetch(`/api/knowledge/${knowledgeBaseId}/documents/${documentId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(tagData),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update document tags')
|
||||
}
|
||||
|
||||
updateDocumentInStore(knowledgeBaseId, documentId, tagData)
|
||||
onDocumentUpdate?.(tagData)
|
||||
|
||||
await fetchTagDefinitions()
|
||||
} catch (error) {
|
||||
logger.error('Error updating document tags:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
[
|
||||
documentData,
|
||||
knowledgeBaseId,
|
||||
documentId,
|
||||
updateDocumentInStore,
|
||||
fetchTagDefinitions,
|
||||
onDocumentUpdate,
|
||||
]
|
||||
)
|
||||
|
||||
const handleRemoveTag = async (index: number) => {
|
||||
const updatedTags = documentTags.filter((_, i) => i !== index)
|
||||
handleTagsChange(updatedTags)
|
||||
|
||||
try {
|
||||
await handleSaveDocumentTags(updatedTags)
|
||||
} catch (error) {
|
||||
logger.error('Error removing tag:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const startEditingTag = (index: number) => {
|
||||
const tag = documentTags[index]
|
||||
setEditingTagIndex(index)
|
||||
setEditTagForm({
|
||||
displayName: tag.displayName,
|
||||
fieldType: tag.fieldType,
|
||||
value: tag.value,
|
||||
})
|
||||
setIsCreatingTag(false)
|
||||
}
|
||||
|
||||
const openTagCreator = () => {
|
||||
setEditingTagIndex(null)
|
||||
setEditTagForm({
|
||||
displayName: '',
|
||||
fieldType: 'text',
|
||||
value: '',
|
||||
})
|
||||
setIsCreatingTag(true)
|
||||
}
|
||||
|
||||
const cancelEditingTag = () => {
|
||||
setEditTagForm({
|
||||
displayName: '',
|
||||
fieldType: 'text',
|
||||
value: '',
|
||||
})
|
||||
setEditingTagIndex(null)
|
||||
setIsCreatingTag(false)
|
||||
}
|
||||
|
||||
const hasTagNameConflict = (name: string) => {
|
||||
if (!name.trim()) return false
|
||||
|
||||
return documentTags.some((tag, index) => {
|
||||
if (editingTagIndex !== null && index === editingTagIndex) {
|
||||
return false
|
||||
}
|
||||
return tag.displayName.toLowerCase() === name.trim().toLowerCase()
|
||||
})
|
||||
}
|
||||
|
||||
const availableDefinitions = kbTagDefinitions.filter((def) => {
|
||||
return !documentTags.some(
|
||||
(tag) => tag.displayName.toLowerCase() === def.displayName.toLowerCase()
|
||||
)
|
||||
})
|
||||
|
||||
const tagNameOptions = availableDefinitions.map((def) => ({
|
||||
label: def.displayName,
|
||||
value: def.displayName,
|
||||
}))
|
||||
|
||||
const saveDocumentTag = async () => {
|
||||
if (!editTagForm.displayName.trim() || !editTagForm.value.trim()) return
|
||||
|
||||
const formData = { ...editTagForm }
|
||||
const currentEditingIndex = editingTagIndex
|
||||
const originalTag = currentEditingIndex !== null ? documentTags[currentEditingIndex] : null
|
||||
setEditingTagIndex(null)
|
||||
setIsCreatingTag(false)
|
||||
setIsSavingTag(true)
|
||||
|
||||
try {
|
||||
let targetSlot: string
|
||||
|
||||
if (currentEditingIndex !== null && originalTag) {
|
||||
targetSlot = originalTag.slot
|
||||
} else {
|
||||
const existingDefinition = kbTagDefinitions.find(
|
||||
(def) => def.displayName.toLowerCase() === formData.displayName.toLowerCase()
|
||||
)
|
||||
|
||||
if (existingDefinition) {
|
||||
targetSlot = existingDefinition.tagSlot
|
||||
} else {
|
||||
const serverSlot = await getServerNextSlot(formData.fieldType)
|
||||
if (!serverSlot) {
|
||||
throw new Error(`No available slots for new tag of type '${formData.fieldType}'`)
|
||||
}
|
||||
targetSlot = serverSlot
|
||||
}
|
||||
}
|
||||
|
||||
let updatedTags: DocumentTag[]
|
||||
if (currentEditingIndex !== null) {
|
||||
updatedTags = [...documentTags]
|
||||
updatedTags[currentEditingIndex] = {
|
||||
...updatedTags[currentEditingIndex],
|
||||
displayName: formData.displayName,
|
||||
fieldType: formData.fieldType,
|
||||
value: formData.value,
|
||||
}
|
||||
} else {
|
||||
const newTag: DocumentTag = {
|
||||
slot: targetSlot,
|
||||
displayName: formData.displayName,
|
||||
fieldType: formData.fieldType,
|
||||
value: formData.value,
|
||||
}
|
||||
updatedTags = [...documentTags, newTag]
|
||||
}
|
||||
|
||||
handleTagsChange(updatedTags)
|
||||
|
||||
if (currentEditingIndex !== null && originalTag) {
|
||||
const currentDefinition = kbTagDefinitions.find(
|
||||
(def) => def.displayName.toLowerCase() === originalTag.displayName.toLowerCase()
|
||||
)
|
||||
|
||||
if (currentDefinition) {
|
||||
const updatedDefinition: TagDefinitionInput = {
|
||||
displayName: formData.displayName,
|
||||
fieldType: currentDefinition.fieldType,
|
||||
tagSlot: currentDefinition.tagSlot,
|
||||
_originalDisplayName: originalTag.displayName,
|
||||
}
|
||||
|
||||
if (saveTagDefinitions) {
|
||||
await saveTagDefinitions([updatedDefinition])
|
||||
}
|
||||
await refreshTagDefinitions()
|
||||
}
|
||||
} else {
|
||||
const existingDefinition = kbTagDefinitions.find(
|
||||
(def) => def.displayName.toLowerCase() === formData.displayName.toLowerCase()
|
||||
)
|
||||
|
||||
if (!existingDefinition) {
|
||||
const newDefinition: TagDefinitionInput = {
|
||||
displayName: formData.displayName,
|
||||
fieldType: formData.fieldType,
|
||||
tagSlot: targetSlot as TagSlot,
|
||||
}
|
||||
|
||||
if (saveTagDefinitions) {
|
||||
await saveTagDefinitions([newDefinition])
|
||||
}
|
||||
await refreshTagDefinitions()
|
||||
}
|
||||
}
|
||||
|
||||
await handleSaveDocumentTags(updatedTags)
|
||||
|
||||
setEditTagForm({
|
||||
displayName: '',
|
||||
fieldType: 'text',
|
||||
value: '',
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error saving tag:', error)
|
||||
} finally {
|
||||
setIsSavingTag(false)
|
||||
}
|
||||
}
|
||||
|
||||
const isTagEditing = editingTagIndex !== null || isCreatingTag
|
||||
const tagNameConflict = hasTagNameConflict(editTagForm.displayName)
|
||||
|
||||
const hasTagChanges = () => {
|
||||
if (editingTagIndex === null) return true
|
||||
|
||||
const originalTag = documentTags[editingTagIndex]
|
||||
if (!originalTag) return true
|
||||
|
||||
return (
|
||||
originalTag.displayName !== editTagForm.displayName ||
|
||||
originalTag.value !== editTagForm.value ||
|
||||
originalTag.fieldType !== editTagForm.fieldType
|
||||
)
|
||||
}
|
||||
|
||||
const canSaveTag =
|
||||
editTagForm.displayName.trim() &&
|
||||
editTagForm.value.trim() &&
|
||||
!tagNameConflict &&
|
||||
hasTagChanges()
|
||||
|
||||
const canAddNewTag = kbTagDefinitions.length < MAX_TAG_SLOTS || availableDefinitions.length > 0
|
||||
|
||||
useEffect(() => {
|
||||
if (documentData && tagDefinitions && !isSavingTag) {
|
||||
const rebuiltTags = buildDocumentTags(documentData, tagDefinitions)
|
||||
setDocumentTags(rebuiltTags)
|
||||
}
|
||||
}, [documentData, tagDefinitions, buildDocumentTags, isSavingTag])
|
||||
|
||||
const handleClose = (openState: boolean) => {
|
||||
if (!openState) {
|
||||
setIsCreatingTag(false)
|
||||
setEditingTagIndex(null)
|
||||
setEditTagForm({
|
||||
displayName: '',
|
||||
fieldType: 'text',
|
||||
value: '',
|
||||
})
|
||||
}
|
||||
onOpenChange(openState)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={open} onOpenChange={handleClose}>
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
<div className='flex items-center justify-between'>
|
||||
<span>Document Tags</span>
|
||||
</div>
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody className='!pb-[16px]'>
|
||||
<div className='min-h-0 flex-1 overflow-y-auto'>
|
||||
<div className='space-y-[8px]'>
|
||||
<Label>
|
||||
Tags{' '}
|
||||
<span className='pl-[6px] text-[var(--text-tertiary)]'>
|
||||
{documentTags.length}/{MAX_TAG_SLOTS} slots used
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
{documentTags.length === 0 && !isCreatingTag && (
|
||||
<div className='rounded-[6px] border p-[16px] text-center'>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
No tags added yet. Add tags to help organize this document.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{documentTags.map((tag, index) => (
|
||||
<div key={index} className='space-y-[8px]'>
|
||||
<div
|
||||
className='flex cursor-pointer items-center gap-2 rounded-[4px] border p-[8px] hover:bg-[var(--surface-2)]'
|
||||
onClick={() => startEditingTag(index)}
|
||||
>
|
||||
<span className='min-w-0 truncate text-[12px] text-[var(--text-primary)]'>
|
||||
{tag.displayName}
|
||||
</span>
|
||||
<div className='mb-[-1.5px] h-[14px] w-[1.25px] flex-shrink-0 rounded-full bg-[#3A3A3A]' />
|
||||
<span className='min-w-0 flex-1 truncate text-[11px] text-[var(--text-muted)]'>
|
||||
{tag.value}
|
||||
</span>
|
||||
<div className='flex flex-shrink-0 items-center gap-1'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleRemoveTag(index)
|
||||
}}
|
||||
className='h-4 w-4 p-0 text-[var(--text-muted)] hover:text-[var(--text-error)]'
|
||||
>
|
||||
<Trash className='h-3 w-3' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{editingTagIndex === index && (
|
||||
<div className='space-y-[8px] rounded-[6px] border p-[12px]'>
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
<Label htmlFor={`tagName-${index}`}>Tag Name</Label>
|
||||
{availableDefinitions.length > 0 ? (
|
||||
<Combobox
|
||||
id={`tagName-${index}`}
|
||||
options={tagNameOptions}
|
||||
value={editTagForm.displayName}
|
||||
selectedValue={editTagForm.displayName}
|
||||
onChange={(value) => {
|
||||
const def = kbTagDefinitions.find(
|
||||
(d) => d.displayName.toLowerCase() === value.toLowerCase()
|
||||
)
|
||||
setEditTagForm({
|
||||
...editTagForm,
|
||||
displayName: value,
|
||||
fieldType: def?.fieldType || 'text',
|
||||
})
|
||||
}}
|
||||
placeholder='Enter or select tag name'
|
||||
editable={true}
|
||||
className={cn(tagNameConflict && 'border-[var(--text-error)]')}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
id={`tagName-${index}`}
|
||||
value={editTagForm.displayName}
|
||||
onChange={(e) =>
|
||||
setEditTagForm({ ...editTagForm, displayName: e.target.value })
|
||||
}
|
||||
placeholder='Enter tag name'
|
||||
className={cn(tagNameConflict && 'border-[var(--text-error)]')}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && canSaveTag) {
|
||||
e.preventDefault()
|
||||
saveDocumentTag()
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
cancelEditingTag()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tagNameConflict && (
|
||||
<span className='text-[11px] text-[var(--text-error)]'>
|
||||
A tag with this name already exists
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Type selector commented out - only "text" type is currently supported
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
<Label htmlFor={`tagType-${index}`}>Type</Label>
|
||||
<Input id={`tagType-${index}`} value='Text' disabled className='capitalize' />
|
||||
</div>
|
||||
*/}
|
||||
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
<Label htmlFor={`tagValue-${index}`}>Value</Label>
|
||||
<Input
|
||||
id={`tagValue-${index}`}
|
||||
value={editTagForm.value}
|
||||
onChange={(e) =>
|
||||
setEditTagForm({ ...editTagForm, value: e.target.value })
|
||||
}
|
||||
placeholder='Enter tag value'
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && canSaveTag) {
|
||||
e.preventDefault()
|
||||
saveDocumentTag()
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
cancelEditingTag()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-[8px]'>
|
||||
<Button variant='default' onClick={cancelEditingTag} className='flex-1'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={saveDocumentTag}
|
||||
className='flex-1'
|
||||
disabled={!canSaveTag}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{!isTagEditing && (
|
||||
<Button
|
||||
variant='default'
|
||||
onClick={openTagCreator}
|
||||
disabled={!canAddNewTag}
|
||||
className='w-full'
|
||||
>
|
||||
Add Tag
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{isCreatingTag && (
|
||||
<div className='space-y-[8px] rounded-[6px] border p-[12px]'>
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
<Label htmlFor='newTagName'>Tag Name</Label>
|
||||
{tagNameOptions.length > 0 ? (
|
||||
<Combobox
|
||||
id='newTagName'
|
||||
options={tagNameOptions}
|
||||
value={editTagForm.displayName}
|
||||
selectedValue={editTagForm.displayName}
|
||||
onChange={(value) => {
|
||||
const def = kbTagDefinitions.find(
|
||||
(d) => d.displayName.toLowerCase() === value.toLowerCase()
|
||||
)
|
||||
setEditTagForm({
|
||||
...editTagForm,
|
||||
displayName: value,
|
||||
fieldType: def?.fieldType || 'text',
|
||||
})
|
||||
}}
|
||||
placeholder='Enter or select tag name'
|
||||
editable={true}
|
||||
className={cn(tagNameConflict && 'border-[var(--text-error)]')}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
id='newTagName'
|
||||
value={editTagForm.displayName}
|
||||
onChange={(e) =>
|
||||
setEditTagForm({ ...editTagForm, displayName: e.target.value })
|
||||
}
|
||||
placeholder='Enter tag name'
|
||||
className={cn(tagNameConflict && 'border-[var(--text-error)]')}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && canSaveTag) {
|
||||
e.preventDefault()
|
||||
saveDocumentTag()
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
cancelEditingTag()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tagNameConflict && (
|
||||
<span className='text-[11px] text-[var(--text-error)]'>
|
||||
A tag with this name already exists
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Type selector commented out - only "text" type is currently supported
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
<Label htmlFor='newTagType'>Type</Label>
|
||||
<Input id='newTagType' value='Text' disabled className='capitalize' />
|
||||
</div>
|
||||
*/}
|
||||
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
<Label htmlFor='newTagValue'>Value</Label>
|
||||
<Input
|
||||
id='newTagValue'
|
||||
value={editTagForm.value}
|
||||
onChange={(e) => setEditTagForm({ ...editTagForm, value: e.target.value })}
|
||||
placeholder='Enter tag value'
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && canSaveTag) {
|
||||
e.preventDefault()
|
||||
saveDocumentTag()
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
cancelEditingTag()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{kbTagDefinitions.length >= MAX_TAG_SLOTS &&
|
||||
!kbTagDefinitions.find(
|
||||
(def) =>
|
||||
def.displayName.toLowerCase() === editTagForm.displayName.toLowerCase()
|
||||
) && (
|
||||
<div className='rounded-[4px] border border-amber-500/50 bg-amber-500/10 p-[8px]'>
|
||||
<p className='text-[11px] text-amber-600 dark:text-amber-400'>
|
||||
Maximum tag definitions reached. You can still use existing tag
|
||||
definitions, but cannot create new ones.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='flex gap-[8px]'>
|
||||
<Button variant='default' onClick={cancelEditingTag} className='flex-1'>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={saveDocumentTag}
|
||||
className='flex-1'
|
||||
disabled={
|
||||
!canSaveTag ||
|
||||
isSavingTag ||
|
||||
(kbTagDefinitions.length >= MAX_TAG_SLOTS &&
|
||||
!kbTagDefinitions.find(
|
||||
(def) =>
|
||||
def.displayName.toLowerCase() ===
|
||||
editTagForm.displayName.toLowerCase()
|
||||
))
|
||||
}
|
||||
>
|
||||
{isSavingTag ? (
|
||||
<>
|
||||
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
'Create Tag'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant='default' onClick={() => handleClose(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||
import { AlertCircle, ChevronDown, ChevronUp, Loader2, X } from 'lucide-react'
|
||||
import {
|
||||
Button,
|
||||
@@ -171,13 +172,15 @@ export function EditChunkModal({
|
||||
return (
|
||||
<>
|
||||
<Modal open={isOpen} onOpenChange={handleCloseAttempt}>
|
||||
<ModalContent className='h-[74vh] sm:max-w-[600px]'>
|
||||
<ModalContent size='lg'>
|
||||
<div className='flex items-center justify-between px-[16px] py-[10px]'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<span className='font-medium text-[16px] text-[var(--text-primary)]'>Edit Chunk</span>
|
||||
<DialogPrimitive.Title className='font-medium text-[16px] text-[var(--text-primary)]'>
|
||||
Edit Chunk #{chunk.chunkIndex}
|
||||
</DialogPrimitive.Title>
|
||||
|
||||
<div className='flex flex-shrink-0 items-center gap-[8px]'>
|
||||
{/* Navigation Controls */}
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='flex items-center gap-[6px]'>
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger
|
||||
asChild
|
||||
@@ -188,9 +191,9 @@ export function EditChunkModal({
|
||||
variant='ghost'
|
||||
onClick={() => handleNavigate('prev')}
|
||||
disabled={!canNavigatePrev || isNavigating || isSaving}
|
||||
className='h-8 w-8 p-0'
|
||||
className='h-[16px] w-[16px] p-0'
|
||||
>
|
||||
<ChevronUp className='h-4 w-4' />
|
||||
<ChevronUp className='h-[16px] w-[16px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
@@ -209,9 +212,9 @@ export function EditChunkModal({
|
||||
variant='ghost'
|
||||
onClick={() => handleNavigate('next')}
|
||||
disabled={!canNavigateNext || isNavigating || isSaving}
|
||||
className='h-8 w-8 p-0'
|
||||
className='h-[16px] w-[16px] p-0'
|
||||
>
|
||||
<ChevronDown className='h-4 w-4' />
|
||||
<ChevronDown className='h-[16px] w-[16px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='bottom'>
|
||||
@@ -222,29 +225,21 @@ export function EditChunkModal({
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button variant='ghost' className='h-[16px] w-[16px] p-0' onClick={handleCloseAttempt}>
|
||||
<X className='h-[16px] w-[16px]' />
|
||||
<span className='sr-only'>Close</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
className='h-[16px] w-[16px] p-0'
|
||||
onClick={handleCloseAttempt}
|
||||
>
|
||||
<X className='h-[16px] w-[16px]' />
|
||||
<span className='sr-only'>Close</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form className='flex min-h-0 flex-1 flex-col'>
|
||||
<ModalBody>
|
||||
<div className='space-y-[12px]'>
|
||||
{/* Document Info Section */}
|
||||
<div className='flex items-center gap-3 rounded-lg border p-4'>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<p className='font-medium text-[var(--text-primary)] text-sm'>
|
||||
{document?.filename || 'Unknown Document'}
|
||||
</p>
|
||||
<p className='text-[var(--text-tertiary)] text-xs'>
|
||||
Editing chunk #{chunk.chunkIndex} • Page {currentPage} of {totalPages}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<ModalBody className='!pb-[16px]'>
|
||||
<div className='flex flex-col gap-[8px]'>
|
||||
{/* Error Display */}
|
||||
{error && (
|
||||
<div className='flex items-center gap-2 rounded-md border border-[var(--text-error)]/50 bg-[var(--text-error)]/10 p-3'>
|
||||
@@ -254,25 +249,18 @@ export function EditChunkModal({
|
||||
)}
|
||||
|
||||
{/* Content Input Section */}
|
||||
<div className='space-y-[8px]'>
|
||||
<Label
|
||||
htmlFor='content'
|
||||
className='mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'
|
||||
>
|
||||
Chunk Content
|
||||
</Label>
|
||||
<Textarea
|
||||
id='content'
|
||||
value={editedContent}
|
||||
onChange={(e) => setEditedContent(e.target.value)}
|
||||
placeholder={
|
||||
userPermissions.canEdit ? 'Enter chunk content...' : 'Read-only view'
|
||||
}
|
||||
rows={10}
|
||||
disabled={isSaving || isNavigating || !userPermissions.canEdit}
|
||||
readOnly={!userPermissions.canEdit}
|
||||
/>
|
||||
</div>
|
||||
<Label htmlFor='content'>Chunk</Label>
|
||||
<Textarea
|
||||
id='content'
|
||||
value={editedContent}
|
||||
onChange={(e) => setEditedContent(e.target.value)}
|
||||
placeholder={
|
||||
userPermissions.canEdit ? 'Enter chunk content...' : 'Read-only view'
|
||||
}
|
||||
rows={20}
|
||||
disabled={isSaving || isNavigating || !userPermissions.canEdit}
|
||||
readOnly={!userPermissions.canEdit}
|
||||
/>
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
@@ -298,7 +286,7 @@ export function EditChunkModal({
|
||||
Saving...
|
||||
</>
|
||||
) : (
|
||||
'Save Changes'
|
||||
'Save'
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
@@ -309,7 +297,7 @@ export function EditChunkModal({
|
||||
|
||||
{/* Unsaved Changes Alert */}
|
||||
<Modal open={showUnsavedChangesAlert} onOpenChange={setShowUnsavedChangesAlert}>
|
||||
<ModalContent className='w-[400px]'>
|
||||
<ModalContent size='sm'>
|
||||
<ModalHeader>Unsaved Changes</ModalHeader>
|
||||
<ModalBody>
|
||||
<p className='text-[12px] text-[var(--text-tertiary)]'>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { CreateChunkModal } from './create-chunk-modal/create-chunk-modal'
|
||||
export { DeleteChunkModal } from './delete-chunk-modal/delete-chunk-modal'
|
||||
export { DocumentLoading } from './document-loading'
|
||||
export { DocumentTagsModal } from './document-tags-modal/document-tags-modal'
|
||||
export { EditChunkModal } from './edit-chunk-modal/edit-chunk-modal'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { Circle, CircleOff, Trash2 } from 'lucide-react'
|
||||
import { Tooltip } from '@/components/emcn'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Circle, CircleOff } from 'lucide-react'
|
||||
import { Button, Tooltip, Trash2 } from '@/components/emcn'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
|
||||
|
||||
@@ -42,23 +41,22 @@ export function ActionBar({
|
||||
transition={{ duration: 0.2 }}
|
||||
className={cn('-translate-x-1/2 fixed bottom-6 left-1/2 z-50 transform', className)}
|
||||
>
|
||||
<div className='flex items-center gap-3 rounded-lg border border-gray-200 bg-background px-4 py-2 shadow-sm dark:border-gray-800'>
|
||||
<span className='text-gray-500 text-sm'>{selectedCount} selected</span>
|
||||
<div className='flex items-center gap-[8px] rounded-[10px] border border-[var(--border-strong)] bg-[var(--surface-1)] p-[8px]'>
|
||||
<span className='px-[4px] text-[13px] text-[var(--text-muted)]'>
|
||||
{selectedCount} selected
|
||||
</span>
|
||||
|
||||
<div className='h-4 w-px bg-gray-200 dark:bg-gray-800' />
|
||||
|
||||
<div className='flex items-center gap-1'>
|
||||
<div className='flex items-center gap-[5px]'>
|
||||
{showEnableButton && (
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={onEnable}
|
||||
disabled={isLoading}
|
||||
className='text-gray-500 hover:text-gray-700'
|
||||
className='hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-[8px] bg-[var(--surface-9)] p-0 text-[#868686] hover:bg-[var(--brand-secondary)]'
|
||||
>
|
||||
<Circle className='h-4 w-4' />
|
||||
<Circle className='h-[12px] w-[12px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
@@ -72,12 +70,11 @@ export function ActionBar({
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={onDisable}
|
||||
disabled={isLoading}
|
||||
className='text-gray-500 hover:text-gray-700'
|
||||
className='hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-[8px] bg-[var(--surface-9)] p-0 text-[#868686] hover:bg-[var(--brand-secondary)]'
|
||||
>
|
||||
<CircleOff className='h-4 w-4' />
|
||||
<CircleOff className='h-[12px] w-[12px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>
|
||||
@@ -91,12 +88,11 @@ export function ActionBar({
|
||||
<Tooltip.Trigger asChild>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='sm'
|
||||
onClick={onDelete}
|
||||
disabled={isLoading}
|
||||
className='text-gray-500 hover:text-red-600'
|
||||
className='hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-[8px] bg-[var(--surface-9)] p-0 text-[#868686] hover:bg-[var(--brand-secondary)]'
|
||||
>
|
||||
<Trash2 className='h-4 w-4' />
|
||||
<Trash2 className='h-[12px] w-[12px]' />
|
||||
</Button>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Content side='top'>Delete items</Tooltip.Content>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user