/
alexefan136
/
flowstack
Обзор
Документация
Войти
/
alexefan136
/
flowstack
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
ui/src/lib/flow-types.ts
806 строк
23 KB
Alexander Efanov
upd fix
04 авг 2026, 14:09
04 авг 2026, 14:09
f63ab86
Код
Авторство
О чём код?
// src/lib/flow-types.ts // // Типы для Flow Composer (визуальный редактор) и Predefined Flows. // // ⚠️ AgentEvent канонически определён в ./api.ts. // Здесь оставлена локальная копия для совместимости с flow-компонентами, // которые импортируют напрямую из flow-types. import type { Edge, Node } from "@xyflow/react"; // ============================================================================ // AGENT EVENTS (локальная копия — каноническая в ./api.ts) // ============================================================================ export type AgentEvent = | { type: "agent_start"; agent?: string } | { type: "agent_message"; agent?: string; content?: string } | { type: "agent_done"; agent?: string; output?: string; tokens?: number; model?: string; } | { type: "content"; content?: string; model?: string; agent?: string } | { type: "done"; model?: string; tokens_prompt?: number; tokens_completion?: number; tokens_total?: number; output?: string; } | { type: "error"; error?: string; agent?: string } | { type: "tool_call"; tool?: string; args?: Record<string, unknown> } | { type: "citation"; doc_id?: string; title?: string; score?: number } // Мета-события predefined flows | { type: "experts_done"; experts_count?: number; experts_tokens?: Record<string, number>; } | { type: "options_researched"; options_count?: number; options?: string[]; options_tokens?: Record<string, number>; } | { type: "ideas_generated"; generators_count?: number; generators?: string[]; generators_tokens?: Record<string, number>; } | { type: "flow_done"; flow_id?: string; output?: string; tokens?: number; stages?: Record<string, unknown>; metadata?: Record<string, unknown>; }; // ============================================================================ // FLOW NODE TYPES (визуальный редактор) // ============================================================================ export type FlowNodeType = | "start" | "end" | "agent" | "llm" | "tool" | "condition"; export interface BaseNodeData { label: string; description?: string; [key: string]: unknown; } export interface StartNodeData extends BaseNodeData { type: "start"; } export interface EndNodeData extends BaseNodeData { type: "end"; } export interface AgentNodeData extends BaseNodeData { type: "agent"; agentType: "research" | "writer" | "critic" | "analyst" | "custom"; systemPrompt: string; model?: string; } export interface LLMNodeData extends BaseNodeData { type: "llm"; model: string; prompt: string; temperature: number; } export interface ToolNodeData extends BaseNodeData { type: "tool"; toolType: | "web_search" | "code_executor" | "rag_search" | "http_request" | "custom"; config: Record<string, unknown>; } export interface ConditionNodeData extends BaseNodeData { type: "condition"; expression: string; trueBranch?: string; falseBranch?: string; } export type FlowNodeData = | StartNodeData | EndNodeData | AgentNodeData | LLMNodeData | ToolNodeData | ConditionNodeData; export type FlowNode = Node<FlowNodeData>; export type FlowEdge = Edge; // ============================================================================ // PALETTE (для drag-and-drop редактора) // ============================================================================ export interface PaletteItem { type: FlowNodeType; label: string; description: string; icon: string; color: string; defaultData: Partial<FlowNodeData>; } export const PALETTE_ITEMS: PaletteItem[] = [ { type: "start", label: "Start", description: "Точка входа flow", icon: "▶", color: "from-emerald-500 to-teal-500", defaultData: { label: "Start", type: "start" }, }, { type: "end", label: "End", description: "Завершение flow", icon: "⏹", color: "from-red-500 to-rose-500", defaultData: { label: "End", type: "end" }, }, { type: "agent", label: "Agent", description: "ИИ-агент с промптом", icon: "🤖", color: "from-violet-500 to-purple-500", defaultData: { label: "Agent", type: "agent", agentType: "research", systemPrompt: "You are a helpful assistant.", }, }, { type: "llm", label: "LLM", description: "Прямой вызов LLM", icon: "🧠", color: "from-blue-500 to-cyan-500", defaultData: { label: "LLM Call", type: "llm", model: "deepseek-ai/DeepSeek-V4-Pro", prompt: "Answer the question: {{input}}", temperature: 0.7, }, }, { type: "tool", label: "Tool", description: "Внешний инструмент", icon: "🛠️", color: "from-amber-500 to-orange-500", defaultData: { label: "Tool", type: "tool", toolType: "web_search", config: {}, }, }, { type: "condition", label: "Condition", description: "Условное ветвление", icon: "❓", color: "from-pink-500 to-rose-500", defaultData: { label: "Condition", type: "condition", expression: "input.score > 0.8", }, }, ]; // ============================================================================ // FLOW (DB-based, из API) // ============================================================================ /** * DB Flow — как приходит от Engine через GET /api/v1/flows. * Поля в snake_case (FastAPI response). */ export interface Flow { id: string; name: string; description: string; status?: string; workspace_id?: string; /**_nodes/edges как JSON из Engine (может быть null для пустых flows) */ nodes?: FlowNode[] | null; edges?: FlowEdge[] | null; /** Полное определение flow (альтернативный формат) */ definition?: { nodes?: FlowNode[]; edges?: FlowEdge[]; }; created_at?: string; updated_at?: string; /** Legacy поля для совместимости с UI-компонентами */ createdAt?: string; updatedAt?: string; runs?: number; success?: number; } // ============================================================================ // VALIDATION // ============================================================================ export interface ValidationResult { valid: boolean; errors: string[]; warnings: string[]; } export function validateFlow( nodes: FlowNode[], edges: FlowEdge[], ): ValidationResult { const errors: string[] = []; const warnings: string[] = []; const startNodes = nodes.filter((n) => n.data.type === "start"); if (startNodes.length === 0) { errors.push("Flow должен содержать узел Start"); } else if (startNodes.length > 1) { errors.push("Flow должен содержать только один узел Start"); } const endNodes = nodes.filter((n) => n.data.type === "end"); if (endNodes.length === 0) { errors.push("Flow должен содержать узел End"); } const connectedNodeIds = new Set<string>(); edges.forEach((e) => { connectedNodeIds.add(e.source); connectedNodeIds.add(e.target); }); nodes.forEach((n) => { if (!connectedNodeIds.has(n.id) && n.data.type !== "start") { warnings.push(`Узел "${n.data.label}" не связан с flow`); } }); const hasCycle = detectCycle(nodes, edges); if (hasCycle) { errors.push("Flow содержит циклы — это запрещено"); } return { valid: errors.length === 0, errors, warnings }; } function detectCycle(nodes: FlowNode[], edges: FlowEdge[]): boolean { const adj = new Map<string, string[]>(); nodes.forEach((n) => adj.set(n.id, [])); edges.forEach((e) => adj.get(e.source)?.push(e.target)); const visited = new Set<string>(); const recursionStack = new Set<string>(); function dfs(nodeId: string): boolean { visited.add(nodeId); recursionStack.add(nodeId); for (const neighbor of adj.get(nodeId) || []) { if (!visited.has(neighbor)) { if (dfs(neighbor)) return true; } else if (recursionStack.has(neighbor)) { return true; } } recursionStack.delete(nodeId); return false; } for (const node of nodes) { if (!visited.has(node.id) && dfs(node.id)) return true; } return false; } // ============================================================================ // YAML EXPORT // ============================================================================ export function flowToYaml(flow: Flow): string { const lines: string[] = []; lines.push(`name: ${flow.name}`); lines.push(`description: ${flow.description}`); lines.push(""); lines.push("nodes:"); const nodes = flow.definition?.nodes || flow.nodes || []; nodes.forEach((node) => { lines.push(` - id: ${node.id}`); lines.push(` type: ${node.data.type}`); lines.push(` label: "${node.data.label}"`); lines.push( ` position: { x: ${node.position.x}, y: ${node.position.y} }`, ); if (node.data.type === "agent") { const data = node.data as AgentNodeData; lines.push(` agent_type: ${data.agentType}`); lines.push(` system_prompt: |`); data.systemPrompt.split("\n").forEach((line) => { lines.push(` ${line}`); }); } else if (node.data.type === "llm") { const data = node.data as LLMNodeData; lines.push(` model: ${data.model}`); lines.push(` temperature: ${data.temperature}`); lines.push(` prompt: "${data.prompt.replace(/"/g, '\\"')}"`); } else if (node.data.type === "tool") { const data = node.data as ToolNodeData; lines.push(` tool_type: ${data.toolType}`); } else if (node.data.type === "condition") { const data = node.data as ConditionNodeData; lines.push(` expression: "${data.expression}"`); } lines.push(""); }); lines.push("edges:"); const edges = flow.definition?.edges || flow.edges || []; edges.forEach((edge) => { lines.push(` - from: ${edge.source}`); lines.push(` to: ${edge.target}`); if (edge.label) lines.push(` label: "${edge.label}"`); lines.push(""); }); return lines.join("\n"); } // ============================================================================ // PREDEFINED FLOWS // ============================================================================ export type PredefinedFlowCategory = | "research" | "review" | "decision" | "ideation"; /** * Предопределённый flow из FLOW_REGISTRY Engine. * Идентифицируется строковым ID ("research", "brainstorm", etc.). * Запускается через POST /api/v1/runs. */ export interface PredefinedFlow { id: string; name: string; description: string; agents: string[]; category: PredefinedFlowCategory; icon?: string; color?: string; features?: { parallel?: boolean; parallelStage?: string; expectedAgentsCount?: number; }; inputFields?: PredefinedFlowInputField[]; } export interface PredefinedFlowInputField { key: string; label: string; type: "text" | "textarea" | "select" | "number" | "tags"; required?: boolean; placeholder?: string; helpText?: string; options?: { value: string; label: string }[]; defaultValue?: unknown; } /** * Результат non-streaming запуска predefined flow. * Соответствует RunResponse из Engine POST /api/v1/runs. */ export interface PredefinedFlowRunResult { flow_id: string; status: string; output: string; tokens: number; stages: Record<string, unknown>; metadata: Record<string, unknown>; } // ============================================================================ // PREDEFINED FLOWS CONFIG (UI metadata + input forms) // ============================================================================ export const PREDEFINED_FLOWS_CONFIG: Record<string, PredefinedFlow> = { research: { id: "research", name: "Исследование темы", description: "Глубокое исследование с планированием, сбором информации, анализом и проверкой качества", agents: ["planner", "researcher", "analyst", "writer", "reviewer"], category: "research", icon: "🔬", inputFields: [ { key: "topic", label: "Тема исследования", type: "textarea", required: true, placeholder: "Например: Искусственный интеллект в медицине", }, { key: "depth", label: "Глубина анализа", type: "select", defaultValue: "comprehensive", options: [ { value: "quick", label: "Быстрый обзор" }, { value: "standard", label: "Стандартный" }, { value: "comprehensive", label: "Глубокий" }, ], }, { key: "focus_areas", label: "Приоритетные направления", type: "textarea", placeholder: "Опционально: конкретные аспекты для фокуса", }, ], }, review: { id: "review", name: "Критический обзор", description: "Мультиагентный критический обзор контента: критика → ревью → финальный отчёт", agents: ["critic", "reviewer", "writer"], category: "review", icon: "🎯", inputFields: [ { key: "content", label: "Контент для обзора", type: "textarea", required: true, placeholder: "Вставьте текст, статью или документ для обзора", }, { key: "context", label: "Контекст", type: "textarea", placeholder: "Опционально: для чего этот контент, целевая аудитория", }, ], }, debate: { id: "debate", name: "Дебаты", description: "Аргументированные дебаты с позициями ЗА и ПРОТИВ, анализом и вердиктом судьи", agents: ["planner", "debater_pro", "debater_con", "analyst", "judge"], category: "decision", icon: "🗣️", inputFields: [ { key: "topic", label: "Тема дебатов", type: "textarea", required: true, placeholder: "Например: Стоит ли переходить на микросервисы?", }, { key: "context", label: "Контекст", type: "textarea", placeholder: "Дополнительный контекст для дебатов", }, ], }, code_review: { id: "code_review", name: "Code Review", description: "Профессиональное ревью кода с 5 экспертами: исследование, анализ, ревью, критика, отчёт", agents: ["researcher", "analyst", "reviewer", "critic", "writer"], category: "review", icon: "💻", inputFields: [ { key: "code", label: "Код для ревью", type: "textarea", required: true, placeholder: "Вставьте код для ревью", }, { key: "language", label: "Язык программирования", type: "select", defaultValue: "auto-detect", options: [ { value: "auto-detect", label: "Авто-определение" }, { value: "python", label: "Python" }, { value: "javascript", label: "JavaScript/TypeScript" }, { value: "go", label: "Go" }, { value: "rust", label: "Rust" }, { value: "java", label: "Java" }, ], }, { key: "focus", label: "Фокус ревью", type: "select", defaultValue: "comprehensive", options: [ { value: "comprehensive", label: "Комплексный" }, { value: "security", label: "Безопасность" }, { value: "performance", label: "Производительность" }, { value: "architecture", label: "Архитектура" }, { value: "style", label: "Стиль кода" }, ], }, { key: "context", label: "Контекст проекта", type: "textarea", placeholder: "Опционально: описание проекта, технологии", }, ], }, consensus: { id: "consensus", name: "Консенсус экспертов", description: "Параллельное мнение 3 экспертов с синтезом в консенсусное решение", agents: [ "planner", "expert_researcher", "expert_analyst", "expert_critic", "moderator", "debater", "judge", "writer", ], category: "decision", icon: "🤝", features: { parallel: true, parallelStage: "experts", expectedAgentsCount: 3, }, inputFields: [ { key: "topic", label: "Проблема для обсуждения", type: "textarea", required: true, placeholder: "Сложная проблема, требующая мнения нескольких экспертов", }, { key: "context", label: "Контекст", type: "textarea", placeholder: "Дополнительный контекст", }, { key: "constraints", label: "Ограничения", type: "textarea", placeholder: "Бюджет, сроки, ресурсы", }, ], }, compare: { id: "compare", name: "Сравнение вариантов", description: "Параллельное исследование N вариантов с матрицей сравнения и рекомендацией", agents: [ "planner", "option_researcher", "analyst", "critic", "judge", "writer", ], category: "decision", icon: "⚖️", features: { parallel: true, parallelStage: "options_research" }, inputFields: [ { key: "input", label: "Задача для сравнения", type: "textarea", required: true, placeholder: "Например: Выбор БД для e-commerce", }, { key: "options", label: "Варианты (через запятую)", type: "tags", required: true, placeholder: "PostgreSQL, MongoDB, Cassandra", helpText: "Минимум 2 варианта для сравнения", }, { key: "criteria", label: "Критерии сравнения", type: "textarea", placeholder: "Опционально: свои критерии", }, { key: "context", label: "Контекст", type: "textarea", placeholder: "Контекст задачи", }, { key: "constraints", label: "Ограничения", type: "textarea", placeholder: "Бюджет, сроки", }, ], }, brainstorm: { id: "brainstorm", name: "Брейншторм", description: "Генерация идей с 4 параллельными генераторами разных когнитивных стилей", agents: [ "planner", "generator_analogies", "generator_patterns", "generator_creative", "generator_reverse", "synthesizer", "evaluator", "judge", "writer", ], category: "ideation", icon: "💡", features: { parallel: true, parallelStage: "generators", expectedAgentsCount: 4, }, inputFields: [ { key: "topic", label: "Проблема для брейншторма", type: "textarea", required: true, placeholder: "Например: Как увеличить retention в приложении?", }, { key: "context", label: "Контекст", type: "textarea", placeholder: "Описание продукта, метрики", }, { key: "constraints", label: "Ограничения", type: "textarea", placeholder: "Бюджет, команда, сроки", }, { key: "goals", label: "Цели", type: "textarea", placeholder: "Чего хотим достичь", }, { key: "top_k", label: "Сколько идей отобрать", type: "number", defaultValue: 10, }, ], }, analyze: { id: "analyze", name: "Глубокий анализ", description: "Root cause анализ, системные паттерны и прогнозы развития ситуации", agents: [ "planner", "researcher", "analyst_root_cause", "critic", "analyst_forecast", "writer", ], category: "research", icon: "📊", inputFields: [ { key: "topic", label: "Ситуация для анализа", type: "textarea", required: true, placeholder: "Например: Retention упал с 25% до 15% за 3 месяца", helpText: "Также принимаются поля 'input' и 'situation'", }, { key: "context", label: "Контекст", type: "textarea", placeholder: "Дополнительный контекст", }, { key: "data", label: "Имеющиеся данные", type: "textarea", placeholder: "Метрики, логи, данные", }, { key: "questions", label: "Вопросы для ответа", type: "textarea", placeholder: "Конкретные вопросы", }, { key: "timeframe", label: "Горизонт анализа", type: "select", defaultValue: "6 месяцев", options: [ { value: "3 месяца", label: "3 месяца" }, { value: "6 месяцев", label: "6 месяцев" }, { value: "12 месяцев", label: "12 месяцев" }, ], }, ], }, }; // ============================================================================ // CATEGORY METADATA (для UI) // ============================================================================ export const PREDEFINED_FLOW_CATEGORIES: Record< PredefinedFlowCategory, { label: string; icon: string; color: string } > = { research: { label: "Исследование", icon: "🔍", color: "from-blue-500 to-cyan-500", }, review: { label: "Ревью", icon: "✅", color: "from-emerald-500 to-teal-500" }, decision: { label: "Принятие решений", icon: "⚖️", color: "from-amber-500 to-orange-500", }, ideation: { label: "Генерация идей", icon: "💡", color: "from-violet-500 to-purple-500", }, };