feat: AI context, Webhooks, Kanban drag-drop, and UI fixes
This commit is contained in:
@@ -55,19 +55,19 @@
|
||||
<Controls />
|
||||
<MiniMap pannable zoomable class="bg-slate-800 border-slate-700" />
|
||||
<template #node-text="{ data, id }">
|
||||
<FlowNodeRenderer :id="id" :data="data" type="text" :selected="selectedNode?.id === id" @update="updateNodeData" />
|
||||
<FlowNodeRenderer :id="id" :data="data" type="text" :selected="selectedNode?.id === id" @update="updateNodeData" @execute="executeNode" />
|
||||
</template>
|
||||
<template #node-file="{ data, id }">
|
||||
<FlowNodeRenderer :id="id" :data="data" type="file" :selected="selectedNode?.id === id" @update="updateNodeData" />
|
||||
<FlowNodeRenderer :id="id" :data="data" type="file" :selected="selectedNode?.id === id" @update="updateNodeData" @execute="executeNode" />
|
||||
</template>
|
||||
<template #node-task="{ data, id }">
|
||||
<FlowNodeRenderer :id="id" :data="data" type="task" :selected="selectedNode?.id === id" @update="updateNodeData" />
|
||||
<FlowNodeRenderer :id="id" :data="data" type="task" :selected="selectedNode?.id === id" @update="updateNodeData" @execute="executeNode" />
|
||||
</template>
|
||||
<template #node-ai="{ data, id }">
|
||||
<FlowNodeRenderer :id="id" :data="data" type="ai" :selected="selectedNode?.id === id" @update="updateNodeData" />
|
||||
<FlowNodeRenderer :id="id" :data="data" type="ai" :selected="selectedNode?.id === id" @update="updateNodeData" @execute="executeNode" />
|
||||
</template>
|
||||
<template #node-webhook="{ data, id }">
|
||||
<FlowNodeRenderer :id="id" :data="data" type="webhook" :selected="selectedNode?.id === id" @update="updateNodeData" />
|
||||
<FlowNodeRenderer :id="id" :data="data" type="webhook" :selected="selectedNode?.id === id" @update="updateNodeData" @execute="executeNode" />
|
||||
</template>
|
||||
<template #node-git="{ data, id }">
|
||||
<FlowNodeRenderer :id="id" :data="data" type="git" :selected="selectedNode?.id === id" @update="updateNodeData" />
|
||||
@@ -134,6 +134,21 @@
|
||||
<label class="text-[10px] uppercase tracking-wider text-slate-500 block mb-1">Дедлайн (Срок)</label>
|
||||
<input v-model="selectedNode.data.alarm" @blur="commitNodeChange" type="datetime-local" class="w-full bg-slate-800 border border-slate-600 rounded px-2 py-1 text-xs text-slate-200 focus:border-blue-500 outline-none" />
|
||||
</div>
|
||||
<div v-if="selectedNode.type === 'webhook'">
|
||||
<label class="text-[10px] uppercase tracking-wider text-slate-500 block mb-1">Webhook URL</label>
|
||||
<input v-model="selectedNode.data.url" @blur="commitNodeChange" placeholder="https://api.example.com/..." class="w-full bg-slate-800 border border-slate-600 rounded px-2 py-1 text-xs text-slate-200 focus:border-blue-500 outline-none" />
|
||||
</div>
|
||||
<div v-if="selectedNode.type === 'webhook'">
|
||||
<label class="text-[10px] uppercase tracking-wider text-slate-500 block mb-1">Метод</label>
|
||||
<select v-model="selectedNode.data.method" @change="commitNodeChange" class="w-full bg-slate-800 border border-slate-600 rounded px-2 py-1 text-xs text-slate-200">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="selectedNode.type === 'ai'">
|
||||
<label class="text-[10px] uppercase tracking-wider text-slate-500 block mb-1">AI Промпт (Инструкция)</label>
|
||||
<textarea v-model="selectedNode.data.prompt" @blur="commitNodeChange" rows="3" placeholder="Что должен сделать ИИ? (например: Отредактируй этот текст...)" class="w-full bg-slate-800 border border-slate-600 rounded px-2 py-1 text-xs text-slate-200 focus:border-blue-500 outline-none resize-none font-mono"></textarea>
|
||||
</div>
|
||||
<div v-if="selectedNode.data.script_id !== undefined">
|
||||
<label class="text-[10px] uppercase tracking-wider text-slate-500 block mb-1">Скрипт</label>
|
||||
<input v-model="selectedNode.data.script_id" @blur="commitNodeChange" class="w-full bg-slate-800 border border-slate-600 rounded px-2 py-1 text-xs text-slate-200 focus:border-blue-500 outline-none font-mono" />
|
||||
@@ -168,6 +183,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Force rebuild to fix Vite 404 issue -->
|
||||
<script setup>
|
||||
import { VueFlow } from '@vue-flow/core'
|
||||
import { Background } from '@vue-flow/background'
|
||||
@@ -429,34 +445,65 @@ const onNodeDoubleClick = (event) => {
|
||||
const node = event.node
|
||||
|
||||
// 1. Cross-Canvas Navigation Logic
|
||||
// Check if it's a file node or has a URL/file reference to another canvas
|
||||
const targetId = node.data?.file || node.data?.url || ''
|
||||
|
||||
if (targetId && (node.type === 'file' || targetId.includes('.canvas') || targetId.startsWith('/canvas/'))) {
|
||||
// Clean up ID (e.g. "project.canvas" -> "project", "/canvas/project" -> "project")
|
||||
const cleanId = targetId.replace('.canvas', '').replace('/canvas/', '')
|
||||
console.log('[Navigation] Opening cross-linked canvas:', cleanId)
|
||||
navigateTo(`/canvas/${cleanId}`)
|
||||
return // Stop further processing
|
||||
return
|
||||
} else if (node.type === 'file' && !targetId) {
|
||||
alert('У этого узла еще нет ссылки на другую карту. Выберите карту в панели свойств (справа).')
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Task Toggle Logic (fallback)
|
||||
const currentState = node.data?.state || 'todo'
|
||||
|
||||
// Quick toggle workflow: todo -> in_progress -> review -> completed -> todo
|
||||
const nextStateMap = {
|
||||
'todo': 'in_progress',
|
||||
'in_progress': 'review',
|
||||
'review': 'completed',
|
||||
'completed': 'todo',
|
||||
'blocked': 'todo'
|
||||
if (node.type === 'task') {
|
||||
const currentState = node.data?.state || 'todo'
|
||||
const nextStateMap = {
|
||||
'todo': 'in_progress',
|
||||
'in_progress': 'review',
|
||||
'review': 'completed',
|
||||
'completed': 'todo',
|
||||
'blocked': 'todo'
|
||||
}
|
||||
const nextState = nextStateMap[currentState] || 'todo'
|
||||
updateNodeData({ id: node.id, data: { state: nextState } })
|
||||
}
|
||||
}
|
||||
|
||||
const executeNode = async ({ id, type }) => {
|
||||
console.log(`[FlowEditor] Executing node ${id} of type ${type}`)
|
||||
|
||||
const nextState = nextStateMap[currentState] || 'todo'
|
||||
updateNodeData({ id: node.id, data: { state: nextState } })
|
||||
// Collect context from incoming nodes
|
||||
const incomingEdges = elements.value.filter(el => el.target === id)
|
||||
const contextNodes = incomingEdges.map(edge => {
|
||||
const sourceNode = elements.value.find(el => el.id === edge.source)
|
||||
return {
|
||||
id: sourceNode?.id,
|
||||
type: sourceNode?.type,
|
||||
text: sourceNode?.data?.text || sourceNode?.data?.label || ''
|
||||
}
|
||||
}).filter(n => n.text)
|
||||
|
||||
try {
|
||||
const res = await $fetch(`/api/nodes/${id}/execute`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
canvasId: props.canvasId,
|
||||
context: contextNodes
|
||||
},
|
||||
headers: authHeaders.value
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
console.log(`[FlowEditor] Execution successful:`, res.result)
|
||||
// Перезагружаем данные, чтобы увидеть результат
|
||||
await loadData()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[FlowEditor] Execution failed:`, err)
|
||||
alert('Ошибка при выполнении узла: ' + (err.message || 'Неизвестная ошибка'))
|
||||
}
|
||||
}
|
||||
|
||||
const onConnect = (connection) => {
|
||||
|
||||
Reference in New Issue
Block a user