🚀
XFlow
@@ -16,7 +23,7 @@
-
+
{{ loading ? 'Вход...' : 'Войти через Google' }}
@@ -33,7 +40,7 @@ definePageMeta({
layout: 'default'
})
-const { googleSignIn, initAuth, isAuthenticated } = useAuth()
+const { googleSignIn, initAuth, isAuthenticated, isInitializing } = useAuth()
const loading = ref(false)
const signInWithGoogle = async () => {
@@ -52,7 +59,6 @@ onMounted(() => {
if (isAuthenticated.value) {
navigateTo('/')
}
- initAuth()
})
watch(isAuthenticated, (val) => {
diff --git a/app/server/api/ai/coach.post.ts b/app/server/api/ai/coach.post.ts
new file mode 100644
index 0000000..777b081
--- /dev/null
+++ b/app/server/api/ai/coach.post.ts
@@ -0,0 +1,170 @@
+import { requireAuth } from '~/server/utils/auth'
+import { generateAIResponse } from '~/server/utils/ai'
+
+export default defineEventHandler(async (event) => {
+ const user = requireAuth(event)
+ const body = await readBody(event)
+ const { action, text, model } = body
+
+ if (!action || !text) {
+ throw createError({ statusCode: 400, message: 'Action and text are required' })
+ }
+
+ console.log(`[AI Coach] action=${action} userId=${user.id}`)
+
+ try {
+ if (action === 'smart') {
+ const systemPrompt = `You are an expert executive coach. Your job is to transform any vague goal or statement into a high-quality, fully detailed S.M.A.R.T. goal. Keep your response in Russian.
+Use Markdown headers for each element: Specific (Конкретная), Measurable (Измеримая), Achievable (Достижимая), Relevant (Значимая), Time-bound (Ограниченная во времени).
+For Measurable, include a clear checklist with markdown checkboxes (- [ ]) for metrics. At the end, write a brief, inspiring summary.`
+
+ const prompt = `Пожалуйста, преобразуй следующую цель в формат S.M.A.R.T.
+Исходная цель: "${text}"`
+
+ const result = await generateAIResponse(prompt, systemPrompt, model)
+ return { success: true, result }
+
+ } else if (action === 'grow') {
+ const systemPrompt = `You are a professional business coach. Your job is to perform a structured coaching analysis of the user's problem or goal using the G.R.O.W. framework. Keep all content in Russian.
+You must return the response strictly as a JSON object with the following keys:
+{
+ "goal": "Markdown text. Description of the reformulated Goal (G) starting with # 🎯 Цель (Goal).\\n\\nAnswer these questions:\\n- Какой именно цели я хочу достичь?\\n- Какой результат я хочу получить?\\n- Почему достижение этой цели важно?",
+ "options": "Markdown text. Options & Obstacles (O) starting with # 🚧 Варианты (Options).\\n\\nAnswer these questions:\\n- Что именно я могу изменить?\\n- Какие ресурсы мне нужны?\\n- Кто мне может помочь?\\n- А что еще?\\nInclude a checkbox list (- [ ]).",
+ "reality": "Markdown text. Description of the current Situation/Reality (R) starting with # 🔍 Реальность (Reality).\\n\\nAnswer these questions:\\n- Какова текущая ситуация?\\n- Какие препятствия я вижу?\\n- Что мне мешает начать?\\n- Какие у меня уже есть ресурсы?",
+ "wayForward": "Markdown text. Action Plan / Way Forward (W) starting with # 🏁 План действий (Way Forward).\\n\\nAnswer these questions:\\n- Какие самые важные действия необходимо предпринять?\\n- Что я сделаю в первую очередь? Какие сроки?\\nInclude a checkbox list (- [ ])."
+}
+Do NOT include any markdown code blocks (like \`\`\`json) or any extra text before or after the JSON. Return only the JSON object.`
+
+ const prompt = `Проведи GROW-коучинг анализ для следующей проблемы/цели: "${text}"`
+
+ const aiResponse = await generateAIResponse(prompt, systemPrompt, model)
+
+ // Robust JSON extraction
+ let jsonText = aiResponse.trim()
+ if (jsonText.startsWith('```json')) {
+ jsonText = jsonText.substring(7)
+ } else if (jsonText.startsWith('```')) {
+ jsonText = jsonText.substring(3)
+ }
+ if (jsonText.endsWith('```')) {
+ jsonText = jsonText.substring(0, jsonText.length - 3)
+ }
+ jsonText = jsonText.trim()
+
+ try {
+ const parsed = JSON.parse(jsonText)
+ return { success: true, result: parsed }
+ } catch (parseErr) {
+ console.error('Failed to parse AI G.R.O.W. JSON response:', aiResponse)
+ // Fallback: if JSON parsing fails, wrap the text into fields
+ return {
+ success: true,
+ result: {
+ goal: `# 🎯 Цель (Goal)\n\n${text}`,
+ reality: `# 🔍 Реальность (Reality)\n\nНе удалось автоматически разбить ответ AI на JSON. Вот оригинальный ответ:\n\n${aiResponse}`,
+ options: `# 🚧 Варианты\n\n- Рассмотреть альтернативные пути решения вручную`,
+ wayForward: `# 🏁 План действий\n\n- [ ] Детализировать шаги вручную`
+ }
+ }
+ }
+ } else if (action === 'dilts') {
+ const systemPrompt = `You are a professional life and business coach. Your job is to perform a structured analysis of the user's goal or situation using Robert Dilts' Logical Levels (Пирамида логических уровней Дилтса). Keep all content in Russian.
+You must return the response strictly as a JSON object with the following keys:
+{
+ "mission": "Markdown text starting with # 🌟 Миссия (Mission).\\n\\nAnswer these questions:\\n- Ради кого?\\n- Кому еще это важно?\\n(Уровень БЫТЬ)",
+ "identity": "Markdown text starting with # 👤 Идентичность (Identity).\\n\\nAnswer these questions:\\n- Кто я?",
+ "values": "Markdown text starting with # 💎 Ценности (Values).\\n\\nAnswer these questions:\\n- Почему это важно?",
+ "skills": "Markdown text starting with # 🧠 Навыки (Skills).\\n\\nAnswer these questions:\\n- Как? Каким образом?",
+ "actions": "Markdown text starting with # 🎬 Действия (Actions).\\n\\nAnswer these questions:\\n- Что делать? (Уровень ДЕЛАТЬ)",
+ "environment": "Markdown text starting with # 🌍 Окружение (Environment).\\n\\nAnswer these questions:\\n- Где? Когда? С кем? (Уровень ИМЕТЬ)"
+}
+Do NOT include any markdown code blocks (like \`\`\`json) or any extra text before or after the JSON. Return only the JSON object.`
+
+ const prompt = `Проведи коучинговый анализ по логическим уровням Дилтса для следующего запроса: "${text}"`
+
+ const aiResponse = await generateAIResponse(prompt, systemPrompt, model)
+
+ // Robust JSON extraction
+ let jsonText = aiResponse.trim()
+ if (jsonText.startsWith('```json')) {
+ jsonText = jsonText.substring(7)
+ } else if (jsonText.startsWith('```')) {
+ jsonText = jsonText.substring(3)
+ }
+ if (jsonText.endsWith('```')) {
+ jsonText = jsonText.substring(0, jsonText.length - 3)
+ }
+ jsonText = jsonText.trim()
+
+ try {
+ const parsed = JSON.parse(jsonText)
+ return { success: true, result: parsed }
+ } catch (parseErr) {
+ console.error('Failed to parse AI Dilts JSON response:', aiResponse)
+ // Fallback
+ return {
+ success: true,
+ result: {
+ mission: `# 🌟 Миссия (Mission)\n\nРади кого? Кому еще это важно? (Уровень БЫТЬ)\n\n${text}`,
+ identity: `# 👤 Идентичность (Identity)\n\nКто я?`,
+ values: `# 💎 Ценности (Values)\n\nПочему это важно?`,
+ skills: `# 🧠 Навыки (Skills)\n\nКак? Каким образом?`,
+ actions: `# 🎬 Действия (Actions)\n\nЧто делать? (Уровень ДЕЛАТЬ)\n\nНе удалось автоматически разбить ответ AI на JSON:\n\n${aiResponse}`,
+ environment: `# 🌍 Окружение (Environment)\n\nГде? Когда? С кем? (Уровень ИМЕТЬ)`
+ }
+ }
+ }
+ } else if (action === 'eisenhower') {
+ const systemPrompt = `You are a professional productivity expert. Your job is to analyze the user's tasks or list of ideas and prioritize them using the Eisenhower Matrix (Матрица Эйзенхауэра). Keep all content in Russian.
+You must return the response strictly as a JSON object with the following keys:
+{
+ "q1": "Markdown text starting with # 🔥 Важно и Срочно (Do First).\\n\\nIdentify tasks that are both urgent and important. Focus on crises, deadlines, and crucial problems.",
+ "q2": "Markdown text starting with # ⚡ Важно, но Несрочно (Schedule).\\n\\nIdentify tasks that are important but not urgent. Focus on long-term planning, personal growth, and relationships.",
+ "q3": "Markdown text starting with # 🧊 Неважно, но Срочно (Delegate).\\n\\nIdentify tasks that are urgent but not important. Focus on interruptions, minor meetings, and tasks that can be delegated.",
+ "q4": "Markdown text starting with # 💤 Неважно и Несрочно (Eliminate).\\n\\nIdentify tasks that are neither urgent nor important. Focus on time-wasting activities, distractions, and low-value tasks."
+}
+Do NOT include any markdown code blocks (like \`\`\`json) or any extra text before or after the JSON. Return only the JSON object.`
+
+ const prompt = `Распредели следующие задачи/цели по квадрантам Матрицы Эйзенхауэра: "${text}"`
+
+ const aiResponse = await generateAIResponse(prompt, systemPrompt, model)
+
+ // Robust JSON extraction
+ let jsonText = aiResponse.trim()
+ if (jsonText.startsWith('```json')) {
+ jsonText = jsonText.substring(7)
+ } else if (jsonText.startsWith('```')) {
+ jsonText = jsonText.substring(3)
+ }
+ if (jsonText.endsWith('```')) {
+ jsonText = jsonText.substring(0, jsonText.length - 3)
+ }
+ jsonText = jsonText.trim()
+
+ try {
+ const parsed = JSON.parse(jsonText)
+ return { success: true, result: parsed }
+ } catch (parseErr) {
+ console.error('Failed to parse AI Eisenhower JSON response:', aiResponse)
+ // Fallback
+ return {
+ success: true,
+ result: {
+ q1: `# 🔥 Важно и Срочно (Do First)\n\n- [ ] Срочные приоритетные задачи\n\n${text}`,
+ q2: `# ⚡ Важно, но Несрочно (Schedule)\n\n- [ ] Задачи планирования`,
+ q3: `# 🧊 Неважно, но Срочно (Delegate)\n\n- [ ] Задачи для делегирования\n\nНе удалось разобрать ответ ИИ:\n\n${aiResponse}`,
+ q4: `# 💤 Неважно и Несрочно (Eliminate)\n\n- [ ] Задачи, которые можно отложить`
+ }
+ }
+ }
+ } else {
+ throw createError({ statusCode: 400, message: `Unsupported action: ${action}` })
+ }
+ } catch (err: any) {
+ console.error('[AI Coach Error]:', err)
+ throw createError({
+ statusCode: 500,
+ message: err.message || 'AI coach generation failed'
+ })
+ }
+})
diff --git a/app/utils/canvasTransformer.ts b/app/utils/canvasTransformer.ts
new file mode 100644
index 0000000..9033e67
--- /dev/null
+++ b/app/utils/canvasTransformer.ts
@@ -0,0 +1,97 @@
+// Transform Obsidian canvas to Vue Flow elements
+export const transformToFlowElements = (canvasData: any) => {
+ const nodes = (canvasData.nodes || []).map((node: any) => ({
+ id: node.id,
+ type: node.type === 'file' ? 'file' : node.type === 'ai' ? 'ai' : node.type === 'git' ? 'git' : node.type === 'task' ? 'task' : node.type === 'webhook' ? 'webhook' : 'text',
+ position: { x: Number(node.x) || 0, y: Number(node.y) || 0 },
+ data: {
+ ...node,
+ width: node.width || 300,
+ height: node.height || 200,
+ label: node.label || node.text?.slice(0, 20) || node.file || 'Node',
+ // Ensure specific fields exist so the properties panel shows them
+ ...(node.type === 'task' && {
+ text: node.text ?? '',
+ state: node.state ?? 'todo',
+ priority: node.priority ?? 'medium',
+ assigned_to: node.assigned_to ?? '',
+ start_date: node.start_date ?? '',
+ alarm: node.alarm ?? '',
+ script_id: node.script_id ?? ''
+ }),
+ ...(node.type === 'text' && { text: node.text ?? '' }),
+ ...(node.type === 'file' && { file: node.file ?? '' }),
+ ...(node.type === 'ai' && { prompt: node.prompt ?? '' }),
+ ...(node.type === 'webhook' && {
+ script_id: node.script_id ?? '',
+ url: node.url ?? '',
+ method: node.method ?? 'POST'
+ }),
+ ...(node.type === 'git' && {
+ repo: node.repo ?? '',
+ branch: node.branch ?? 'main',
+ message: node.message ?? ''
+ })
+ },
+ style: {
+ width: `${node.width || 300}px`,
+ height: `${node.height || 200}px`,
+ background: node.style?.background || '#1e293b',
+ border: `1px solid ${node.style?.border || '#334155'}`,
+ borderRadius: '8px'
+ }
+ }))
+
+ const edges = (canvasData.edges || []).map((edge: any) => ({
+ id: edge.id,
+ source: edge.fromNode,
+ target: edge.toNode,
+ sourceHandle: edge.fromSide,
+ targetHandle: edge.toSide,
+ animated: true,
+ style: { stroke: '#475569', strokeWidth: 2 }
+ }))
+
+ return [...nodes, ...edges]
+}
+
+// Transform Vue Flow elements back to Obsidian canvas
+export const transformToObsidianCanvas = (flowElements: any[]) => {
+ const nodes = flowElements.filter(el => el.position !== undefined).map(node => ({
+ id: node.id,
+ type: node.type === 'file' ? 'file' : node.type === 'task' ? 'task' : node.type === 'ai' ? 'ai' : node.type === 'webhook' ? 'webhook' : node.type === 'git' ? 'git' : 'text',
+ x: Math.round(Number(node.position.x) || 0),
+ y: Math.round(Number(node.position.y) || 0),
+ width: node.data.width || 300,
+ height: node.data.height || 200,
+ text: node.data.text,
+ label: node.data.label,
+ file: node.data.file,
+ state: node.data.state,
+ priority: node.data.priority,
+ assigned_to: node.data.assigned_to,
+ start_date: node.data.start_date,
+ alarm: node.data.alarm,
+ script_id: node.data.script_id,
+ prompt: node.data.prompt,
+ url: node.data.url,
+ method: node.data.method,
+ repo: node.data.repo,
+ branch: node.data.branch,
+ message: node.data.message,
+ style: {
+ background: node.style?.background || '#1e293b',
+ border: node.style?.border?.replace('1px solid ', '') || '#334155'
+ }
+ }))
+
+ const edges = flowElements.filter(el => el.source && el.target).map(edge => ({
+ id: edge.id,
+ fromNode: edge.source,
+ fromSide: edge.sourceHandle || 'right',
+ toNode: edge.target,
+ toSide: edge.targetHandle || 'left'
+ }))
+
+ return { nodes, edges }
+}
diff --git a/coaching_session_details.md b/coaching_session_details.md
new file mode 100644
index 0000000..a1afab9
--- /dev/null
+++ b/coaching_session_details.md
@@ -0,0 +1,85 @@
+# Подробный отчет о сессии: Интеграция коучинговых фреймворков и инструментов планирования в XFlow
+
+Сегодня была проведена большая работа по интеграции и адаптации коучинговых фреймворков (**S.M.A.R.T.**, **G.R.O.W.**, **Пирамида Дилтса**), а также инструментов приоритизации и планирования (**Матрица Эйзенхауэра**, **Диаграмма Ганта**) в интерактивный редактор интеллект-карт XFlow. Также был осуществлен глубокий рефакторинг монолитного компонента редактора для улучшения структуры проекта.
+
+---
+
+## 🛠️ Внесенные изменения по файлам
+
+### 1. Серверная часть (Бэкенд AI)
+
+#### [MODIFY] [coach.post.ts](file:///c:/Users/ormix/StudioProjects/XFlow/app/server/api/ai/coach.post.ts)
+* **Добавлен эндпоинт коучинга:** Обрабатывает POST-запросы к `/api/ai/coach`.
+* **Интеграция S.M.A.R.T.:** Генерирует оптимизированную цель в формате Markdown с прописанными критериями и чек-листом для метрик.
+* **Интеграция G.R.O.W. с кастомными вопросами:** Генерирует строго валидный JSON-ответ с 4 уровнями (Цель, Варианты, Реальность, План), основываясь на наводящих вопросах пользователя.
+* **Интеграция Пирамиды логических уровней Дилтса:** Добавлен экшен `dilts` для генерации 6 уровней (Миссия, Идентичность, Ценности, Навыки, Действия, Окружение) на русском языке.
+* **Интеграция Матрицы Эйзенхауэра:** Добавлен экшен `eisenhower` для классификации задач пользователя по 4 квадрантам (Важно/Срочно, Важно/Несрочно, Неважно/Срочно, Неважно/Несрочно).
+* **Парсинг JSON:** Настроен робастный парсер, удаляющий лишние Markdown-блоки кода (\`\`\`json), чтобы гарантировать стабильность работы фронтенда.
+
+---
+
+### 2. Клиентские компоненты (Vue Flow Редактор)
+
+#### [NEW] [canvasTransformer.ts](file:///c:/Users/ormix/StudioProjects/XFlow/app/utils/canvasTransformer.ts)
+* Вынесена чистая логика преобразования данных между Obsidian Canvas JSON и внутренним форматом реактивных элементов Vue Flow.
+* Добавлена поддержка сериализации/десериализации нового свойства задачи `start_date` (дата начала задачи) для синхронизации с Obsidian Canvas.
+
+#### [NEW] [NodePalette.vue](file:///c:/Users/ormix/StudioProjects/XFlow/app/components/editor/NodePalette.vue)
+* Изолирован левый сайдбар со списком доступных типов узлов (Текст, Файл, Задача, AI, Webhook, Git) и драг-событиями.
+
+#### [NEW] [NodePropertiesPanel.vue](file:///c:/Users/ormix/StudioProjects/XFlow/app/components/editor/NodePropertiesPanel.vue)
+* Изолирована правая панель свойств выделенного узла.
+* Добавлен выбор даты начала (`start_date`) с типом `datetime-local` для задач, что позволяет планировать временные интервалы.
+* **Интеграция AI Коуча:** Добавлен блок быстрого вызова ИИ-помощника с 4 инструментами: **SMART**, **GROW**, **Дилтс** и **Эйзенхауэр** в компактном виде (4 колонки).
+
+#### [NEW] [CoachingWizardModal.vue](file:///c:/Users/ormix/StudioProjects/XFlow/app/components/editor/CoachingWizardModal.vue)
+* Создано модальное окно интерактивного конструктора (Wizard) с четырьмя вкладками:
+ 1. **🎯 SMART Цель** (S.M.A.R.T.)
+ 2. **🌱 GROW Сессия** (Goal -> Options -> Reality -> Way Forward)
+ 3. **📐 Пирамида Дилтса** (Миссия -> Идентичность -> Ценности -> Навыки -> Действия -> Окружение)
+ 4. **📊 Матрица Эйзенхауэра** (Важно/Срочно -> Важно/Несрочно -> Неважно/Срочно -> Неважно/Несрочно)
+* Для всех вкладок поддерживается как ручной ввод, так и генерация/оптимизация через AI Coach.
+
+#### [NEW] [GanttChartDrawer.vue](file:///c:/Users/ormix/StudioProjects/XFlow/app/components/editor/GanttChartDrawer.vue)
+* Создан выдвижной таймлайн (Gantt Chart) внизу экрана, отображающий все задачи, имеющие даты выполнения (`start_date` и/или `alarm`).
+* Поддерживает:
+ * Синхронизированную горизонтальную прокрутку заголовка дней и строк задач.
+ * Автоматический расчет временного диапазона с ограничением в 30 дней.
+ * Цветовую схему полос в зависимости от состояния задачи (todo, in_progress, review, blocked, completed).
+ * Клик по полосе или названию задачи для автоматической фокусировки и центрирования камеры Vue Flow на соответствующем узле.
+
+#### [MODIFY] [FlowEditor.vue](file:///c:/Users/ormix/StudioProjects/XFlow/app/components/FlowEditor.vue)
+* Очищен от 1000+ строк разметки сайдбаров и модалок, замененных на вызовы новых изолированных компонентов.
+* **Новые кнопки на панели инструментов:** Добавлены `🎯 +SMART`, `🌱 +GROW`, `📐 +Дилтс` и `📊 +Эйзенхауэр` для быстрого размещения на холсте пустых структурированных шаблонов.
+* **Связывание событий:** Подключены методы для сохранения, обновления, экспорта, Git-синхронизации, ручного добавления и ИИ-генерации для всех добавленных фреймворков.
+* **Интеграция Диаграммы Ганта:** Внедрен компонент `GanttChartDrawer`, настроен обработчик `selectAndCenterNode` с плавным переходом камеры к выбранному узлу с помощью хуков `findNode` и `setTransform` из `@vue-flow/core`.
+
+---
+
+## 📊 Описание добавленных коучинговых и планинговых инструментов
+
+### 1. Пирамида логических уровней Дилтса
+Каждый узел создается со своим стилем (фон, цвет рамки) и предзаполненным Markdown-шаблоном:
+* **🌟 Миссия (Mission)** — уровень БЫТЬ (фиолетовый). *Вопросы:* Ради кого? Кому еще это важно?
+* **👤 Идентичность (Identity)** (синий). *Вопросы:* Кто я?
+* **💎 Ценности (Values)** (голубой). *Вопросы:* Почему это важно?
+* **🧠 Навыки (Skills)** (бирюзовый). *Вопросы:* Как? Каким образом?
+* **🎬 Действия (Actions)** — уровень ДЕЛАТЬ (зеленый). *Вопросы:* Что делать?
+* **🌍 Окружение (Environment)** — уровень ИМЕТЬ (серый, узел задачи). *Вопросы:* Где? Когда? С кем?
+
+### 2. Матрица Эйзенхауэра
+Размещает на холсте 4 квадранта (2x2 сетку узлов задач) с четко заданными позициями для предотвращения наложений:
+* **🔥 Важно и Срочно (Сделать)** — красный цвет.
+* **⚡ Важно, но Несрочно (Запланировать)** — синий цвет.
+* **🧊 Неважно, но Срочно (Делегировать)** — оранжевый цвет.
+* **💤 Неважно и Несрочно (Отложить/Удалить)** — серый цвет.
+
+### 3. Диаграмма Ганта
+* Отображает временную шкалу задач для наглядного планирования.
+* Позволяет быстро перемещаться к любой задаче на холсте одним кликом.
+
+---
+
+## 🧪 Статус сборки и готовность
+* Весь проект успешно проходит стадию компиляции (`npm run build`).
+* Локальный сервер `npm run dev` готов к тестированию всех функций.