FlowAPI / FlowAPI.Application /Services /TemplateLibrary.cs
danylokhodus's picture
Optimize prompts for recursive roadmap generation and add goal templates
e4a761a
Raw
History Blame Contribute Delete
32.2 kB
using System;
using System.Collections.Generic;
using FlowAPI.Application.DTOs.Graph;
using FlowAPI.Domain.Entities;
using FlowAPI.Domain.Enums;
namespace FlowAPI.Application.Services
{
public static class TemplateLibrary
{
private class TemplateNodeDef
{
public string TempId { get; set; } = string.Empty;
public string Label { get; set; } = string.Empty;
public double PosX { get; set; }
public double PosY { get; set; }
public string Type { get; set; } = "sketch";
public string Color { get; set; } = "white";
public string Description { get; set; } = string.Empty;
public string DecisionJson { get; set; } = string.Empty;
public double? Width { get; set; }
public double? Height { get; set; }
}
private class TemplateEdgeDef
{
public string FromTempId { get; set; } = string.Empty;
public string ToTempId { get; set; } = string.Empty;
public string Condition { get; set; } = "True";
}
private class TemplateDef
{
public string Key { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
public string Category { get; set; } = string.Empty;
public List<TemplateNodeDef> Nodes { get; set; } = new();
public List<TemplateEdgeDef> Edges { get; set; } = new();
}
private static readonly Dictionary<string, TemplateDef> Templates = new()
{
{
"startup",
new TemplateDef
{
Key = "startup",
Title = "Запуск Стартапу",
Description = "Шлях від формулювання ціннісної пропозиції та CustDev до запуску MVP та перших 100 користувачів.",
Icon = "Rocket",
Category = "Business",
Nodes = new List<TemplateNodeDef>
{
new() { TempId = "vprop", Label = "Формулювання ціннісної пропозиції", PosX = 250, PosY = 50, Color = "purple", Description = "Чітко опишіть, яку проблему вирішує ваш продукт і для кого." },
new() { TempId = "custdev", Label = "CustDev: Інтерв'ю з клієнтами", PosX = 250, PosY = 170, Color = "blue", Description = "Проведіть принаймні 10 інтерв'ю з потенційними користувачами." },
new() { TempId = "fit", Label = "Чи підтверджено проблему?", PosX = 250, PosY = 290, Type = "condition", DecisionJson = "{\"options\":[{\"id\":\"opt-yes\",\"label\":\"Так (Є інтерес)\"},{\"id\":\"opt-no\",\"label\":\"Ні (Потрібен півот)\"}],\"selected\":[],\"multiSelect\":false}" },
new() { TempId = "mvp", Label = "Розробка MVP", PosX = 100, PosY = 420, Color = "green", Description = "Створіть мінімально життєздатний продукт з однією ключовою функцією." },
new() { TempId = "pivot", Label = "Півот ідеї", PosX = 400, PosY = 420, Color = "orange", Description = "Змініть напрямок або цільову аудиторію на основі фідбеку з інтерв'ю." },
new() { TempId = "landing", Label = "Лендинг та збір заявок", PosX = 100, PosY = 550, Color = "pink", Description = "Налаштуйте просту сторінку для збору контактів зацікавлених клієнтів." },
new() { TempId = "users", Label = "Залучення перших 100 користувачів", PosX = 100, PosY = 680, Color = "yellow", Description = "Використовуйте безкоштовні канали (соцмережі, профільні форуми, зв'язки)." }
},
Edges = new List<TemplateEdgeDef>
{
new() { FromTempId = "vprop", ToTempId = "custdev" },
new() { FromTempId = "custdev", ToTempId = "fit" },
new() { FromTempId = "fit", ToTempId = "mvp", Condition = "True" },
new() { FromTempId = "fit", ToTempId = "pivot", Condition = "False" },
new() { FromTempId = "pivot", ToTempId = "vprop" },
new() { FromTempId = "mvp", ToTempId = "landing" },
new() { FromTempId = "landing", ToTempId = "users" }
}
}
},
{
"software",
new TemplateDef
{
Key = "software",
Title = "Розробка ПЗ (Web App)",
Description = "Стандартний життєвий цикл розробки веб-додатку: вимоги, дизайн, UI/UX, API, інтеграція та реліз.",
Icon = "Code",
Category = "Development",
Nodes = new List<TemplateNodeDef>
{
new() { TempId = "req", Label = "Збір та аналіз вимог", PosX = 250, PosY = 50, Color = "blue", Description = "Опишіть функціонал проекту та підготуйте ТЗ." },
new() { TempId = "arch", Label = "Архітектура та проектування БД", PosX = 250, PosY = 170, Color = "purple", Description = "Спроектуйте схему бази даних та виберіть стек технологій." },
new() { TempId = "uiux", Label = "UI/UX макети та дизайн", PosX = 100, PosY = 300, Color = "pink", Description = "Створіть клікабельний прототип у Figma." },
new() { TempId = "api", Label = "Розробка API та Бекенду", PosX = 400, PosY = 300, Color = "yellow", Description = "Реалізуйте серверу логіку, авторизацію та базу даних." },
new() { TempId = "integ", Label = "Інтеграція та тестування", PosX = 250, PosY = 440, Color = "green", Description = "З'єднайте фронтенд з бекендом та проведіть ручне тестування." },
new() { TempId = "staging", Label = "Деплой на Staging сервер", PosX = 250, PosY = 560, Color = "orange", Description = "Розгорніть тестову версію додатку для фінальної перевірки." },
new() { TempId = "prod", Label = "Реліз у Production та моніторинг", PosX = 250, PosY = 680, Color = "green", Description = "Запустіть додаток для реальних користувачів та підключіть аналітику." }
},
Edges = new List<TemplateEdgeDef>
{
new() { FromTempId = "req", ToTempId = "arch" },
new() { FromTempId = "arch", ToTempId = "uiux" },
new() { FromTempId = "arch", ToTempId = "api" },
new() { FromTempId = "uiux", ToTempId = "integ" },
new() { FromTempId = "api", ToTempId = "integ" },
new() { FromTempId = "integ", ToTempId = "staging" },
new() { FromTempId = "staging", ToTempId = "prod" }
}
}
},
{
"language",
new TemplateDef
{
Key = "language",
Title = "Вивчення Іноземної Мови",
Description = "Методика освоєння мови через щоденні звички, проходження базового курсу та вихід у практику.",
Icon = "Languages",
Category = "Education",
Nodes = new List<TemplateNodeDef>
{
new() { TempId = "goal", Label = "Визначення цілей та ресурсів", PosX = 250, PosY = 50, Color = "purple", Description = "Оберіть додатки, підручники та поставте чітку ціль (наприклад, рівень B1)." },
new() { TempId = "words", Label = "Вчити 15 нових слів щодня", PosX = 100, PosY = 170, Type = "habit", Color = "blue", Description = "Використовуйте картки Anki або Quizlet для регулярного повторення.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Використовуйте картки Anki або Quizlet для регулярного повторення.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "listen", Label = "Слухати подкаст / дивитись відео", PosX = 400, PosY = 170, Type = "habit", Color = "pink", Description = "Принаймні 20 хвилин фонового або активного слухання щодня.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Принаймні 20 хвилин фонового або активного слухання щодня.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "course", Label = "Завершити курс для початківців", PosX = 250, PosY = 300, Color = "yellow", Description = "Пройти базову граматику та отримати розуміння структури мови." },
new() { TempId = "speak", Label = "Розмовна сесія (1 раз на тиждень)", PosX = 100, PosY = 430, Type = "habit", Color = "green", Description = "Знайдіть партнера для спілкування на Tandem або викладача.", DecisionJson = "{\"resetRule\":{\"type\":\"weekly\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Знайдіть партнера для спілкування на Tandem або викладача.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "read", Label = "Прочитати першу коротку розповідь", PosX = 400, PosY = 430, Color = "orange", Description = "Оберіть адаптовану книгу під ваш поточний рівень." },
new() { TempId = "exam", Label = "Скласти тестовий іспит", PosX = 250, PosY = 560, Color = "green", Description = "Перевірте свій прогрес за допомогою офіційного пробного тесту." }
},
Edges = new List<TemplateEdgeDef>
{
new() { FromTempId = "goal", ToTempId = "words" },
new() { FromTempId = "goal", ToTempId = "listen" },
new() { FromTempId = "words", ToTempId = "course" },
new() { FromTempId = "listen", ToTempId = "course" },
new() { FromTempId = "course", ToTempId = "speak" },
new() { FromTempId = "course", ToTempId = "read" },
new() { FromTempId = "speak", ToTempId = "exam" },
new() { FromTempId = "read", ToTempId = "exam" }
}
}
},
{
"fitness",
new TemplateDef
{
Key = "fitness",
Title = "Фітнес та Здорове Тіло",
Description = "Збалансований план тренувань, контролю харчування та циклічного аналізу результатів.",
Icon = "Activity",
Category = "Health",
Nodes = new List<TemplateNodeDef>
{
new() { TempId = "setup", Label = "Поставити ціль та розрахувати КБЖВ", PosX = 250, PosY = 50, Color = "blue", Description = "Визначте добову норму калорій залежно від мети (схуднення чи набір маси)." },
new() { TempId = "plan", Label = "Скласти план тренувань та раціон", PosX = 250, PosY = 170, Color = "purple", Description = "Виберіть 3 дні для занять та підготуйте перелік здорових страв." },
new() { TempId = "diet", Label = "Трекати щоденне харчування та воду", PosX = 100, PosY = 300, Type = "habit", Color = "green", Description = "Записуйте все з'їдене в MyFitnessPal та пийте достатньо води.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Записуйте все з'їдене в MyFitnessPal та пийте достатньо води.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "workout", Label = "Силове або кардіо тренування", PosX = 400, PosY = 300, Type = "habit", Color = "orange", Description = "Виконуйте заплановане тренування 3 рази на тиждень.", DecisionJson = "{\"resetRule\":{\"type\":\"weekly\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Виконуйте заплановане тренування 3 рази на тиждень.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "check", Label = "Контрольне зважування та заміри (4 тижні)", PosX = 250, PosY = 430, Color = "yellow", Description = "Порівняйте поточну вагу та об'єми тіла з початковими показниками." },
new() { TempId = "satisfied", Label = "Чи є прогрес задовільним?", PosX = 250, PosY = 550, Type = "condition", DecisionJson = "{\"options\":[{\"id\":\"opt-yes\",\"label\":\"Так (Продовжуємо)\"},{\"id\":\"opt-no\",\"label\":\"Ні (Потрібні коригування)\"}],\"selected\":[],\"multiSelect\":false}" },
new() { TempId = "advance", Label = "Ускладнити програму тренувань", PosX = 100, PosY = 680, Color = "pink", Description = "Додайте вагу на снарядах або збільшіть інтенсивність." },
new() { TempId = "adjust", Label = "Скоригувати КБЖВ та план їжі", PosX = 400, PosY = 680, Color = "yellow", Description = "Зменшіть або збільшіть калорійність раціону на 10%." }
},
Edges = new List<TemplateEdgeDef>
{
new() { FromTempId = "setup", ToTempId = "plan" },
new() { FromTempId = "plan", ToTempId = "diet" },
new() { FromTempId = "plan", ToTempId = "workout" },
new() { FromTempId = "diet", ToTempId = "check" },
new() { FromTempId = "workout", ToTempId = "check" },
new() { FromTempId = "check", ToTempId = "satisfied" },
new() { FromTempId = "satisfied", ToTempId = "advance", Condition = "True" },
new() { FromTempId = "satisfied", ToTempId = "adjust", Condition = "False" },
new() { FromTempId = "adjust", ToTempId = "plan" }
}
}
},
{
"gamedev",
new TemplateDef
{
Key = "gamedev",
Title = "Створення Інді-Гри",
Description = "Повний цикл геймдеву від ідеї та прототипу до створення сторінки в Steam та релізу.",
Icon = "Gamepad",
Category = "Creative",
Nodes = new List<TemplateNodeDef>
{
new() { TempId = "concept", Label = "Концепт та написання GDD", PosX = 250, PosY = 50, Color = "pink", Description = "Сформулюйте основну ідею гри та опишіть її механіки в дизайн-документі." },
new() { TempId = "proto", Label = "Прототип базового геймплею", PosX = 250, PosY = 170, Color = "purple", Description = "Створіть «сіру коробку» на рушії, щоб перевірити, чи весело грати." },
new() { TempId = "art", Label = "Стилістика та створення артів", PosX = 100, PosY = 300, Color = "orange", Description = "Визначте художній стиль та підготуйте перші 2D/3D моделі." },
new() { TempId = "level", Label = "Дизайн рівнів та звуковий супровід", PosX = 400, PosY = 300, Color = "blue", Description = "Створіть тестовий рівень та додайте базові звукові ефекти." },
new() { TempId = "playable", Label = "Перша грабельна демо-версія", PosX = 250, PosY = 430, Color = "yellow", Description = "Зберіть стабільний білд для тестування друзями." },
new() { TempId = "test", Label = "Плейтести та виправлення багів", PosX = 100, PosY = 560, Type = "habit", Color = "pink", Description = "Щодня грайте, збирайте фідбек та виправляйте помилки.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Щодня грайте, збирайте фідбек та виправляйте помилки.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "steam", Label = "Створення сторінки в Steam", PosX = 400, PosY = 560, Color = "yellow", Description = "Підготуйте красиві скріншоти, трейлер та запустіть сторінку для збору Wishlists." },
new() { TempId = "release", Label = "Реліз гри в Steam", PosX = 250, PosY = 690, Color = "green", Description = "Опублікуйте фінальний білд та підтримайте запуск маркетинговою кампанією." }
},
Edges = new List<TemplateEdgeDef>
{
new() { FromTempId = "concept", ToTempId = "proto" },
new() { FromTempId = "proto", ToTempId = "art" },
new() { FromTempId = "proto", ToTempId = "level" },
new() { FromTempId = "art", ToTempId = "playable" },
new() { FromTempId = "level", ToTempId = "playable" },
new() { FromTempId = "playable", ToTempId = "test" },
new() { FromTempId = "playable", ToTempId = "steam" },
new() { FromTempId = "test", ToTempId = "release" },
new() { FromTempId = "steam", ToTempId = "release" }
}
}
},
{
"finance",
new TemplateDef
{
Key = "finance",
Title = "Фінансова Свобода",
Description = "Покроковий план досягнення фінансової незалежності: облік витрат, подушка безпеки та інвестиції.",
Icon = "DollarSign",
Category = "Finance",
Nodes = new List<TemplateNodeDef>
{
new() { TempId = "audit", Label = "Аудит активів та боргових зобов'язань", PosX = 250, PosY = 50, Color = "yellow", Description = "Запишіть усі свої збереження, майно та кредити." },
new() { TempId = "track", Label = "Щоденний облік усіх витрат", PosX = 100, PosY = 170, Type = "habit", Color = "blue", Description = "Записуйте кожну покупку в мобільний додаток або Excel таблицю.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Записуйте кожну покупку в мобільний додаток або Excel таблицю.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "emergency", Label = "Сформувати подушку на 3 місяці", PosX = 300, PosY = 170, Color = "orange", Description = "Розрахуйте мінімальний бюджет життя та накопичте цю суму на депозиті." },
new() { TempId = "debt", Label = "Виплатити всі споживчі кредити", PosX = 250, PosY = 290, Color = "pink", Description = "Спрямуйте вільні кошти на швидке закриття боргів з найвищими відсотками." },
new() { TempId = "invest", Label = "Інвестувати 20% від доходу", PosX = 100, PosY = 420, Type = "habit", Color = "green", Description = "Регулярно купуйте надійні індекси (ETF) або облігації.", DecisionJson = "{\"resetRule\":{\"type\":\"monthly\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Регулярно купуйте надійні індекси (ETF) або облігації.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "passive", Label = "Створити джерело пасивного доходу", PosX = 400, PosY = 420, Color = "purple", Description = "Створіть власний блог, курс, невеликий онлайн-сервіс чи придбайте дивідендні акції." },
new() { TempId = "freedom", Label = "Досягти фінансової незалежності", PosX = 250, PosY = 550, Color = "green", Description = "Коли пасивний дохід повністю перекриє ваші щомісячні витрати." }
},
Edges = new List<TemplateEdgeDef>
{
new() { FromTempId = "audit", ToTempId = "track" },
new() { FromTempId = "audit", ToTempId = "emergency" },
new() { FromTempId = "track", ToTempId = "debt" },
new() { FromTempId = "emergency", ToTempId = "debt" },
new() { FromTempId = "debt", ToTempId = "invest" },
new() { FromTempId = "debt", ToTempId = "passive" },
new() { FromTempId = "invest", ToTempId = "freedom" },
new() { FromTempId = "passive", ToTempId = "freedom" }
}
}
},
{
"writing",
new TemplateDef
{
Key = "writing",
Title = "Написання та Публікація Книги",
Description = "Шлях автора: від розробки сюжету та персонажів до щоденного написання, редактури та релізу.",
Icon = "BookOpen",
Category = "Creative",
Nodes = new List<TemplateNodeDef>
{
new() { TempId = "outline", Label = "Побудова сюжету та анкет персонажів", PosX = 250, PosY = 50, Color = "pink", Description = "Напишіть синопсис та детально розпишіть структуру розділів книги." },
new() { TempId = "words", Label = "Писати по 1000 слів щодня", PosX = 100, PosY = 170, Type = "habit", Color = "purple", Description = "Формуйте звичку писати без критики та внутрішнього редактора.", DecisionJson = "{\"resetRule\":{\"type\":\"daily\",\"time\":\"00:00\"},\"createdAt\":\"" + DateTime.UtcNow.ToString("o") + "\",\"description\":\"Формуйте звичку писати без критики та внутрішнього редактора.\",\"tags\":[],\"status\":\"Todo\",\"progress\":0}" },
new() { TempId = "draft", Label = "Завершити першу чернетку", PosX = 350, PosY = 170, Color = "yellow", Description = "Напишіть повну історію від початку до кінця (орієнтовно 50-80 тис. слів)." },
new() { TempId = "edit", Label = "Самостійна редактура та вичитка", PosX = 250, PosY = 300, Color = "orange", Description = "Виправте логічні помилки, покращіть діалоги та стиль оповідання." },
new() { TempId = "beta", Label = "Отримати фідбек від бета-рідерів", PosX = 250, PosY = 420, Color = "blue", Description = "Дайте почитати книгу кільком людям із вашої цільової аудиторії." },
new() { TempId = "professional", Label = "Професійна коректура та верстка", PosX = 250, PosY = 540, Color = "pink", Description = "Знайдіть професійного редактора для виправлення граматики та підготовки макету." },
new() { TempId = "cover", Label = "Створення обкладинки та форматування", PosX = 100, PosY = 660, Color = "purple", Description = "Замовте обкладинку в дизайнера та підготуйте формати ePub, PDF." },
new() { TempId = "publish", Label = "Публікація та запуск маркетингу", PosX = 350, PosY = 660, Color = "green", Description = "Опублікуйте книгу на Amazon / Litres та запустіть промо-акції." }
},
Edges = new List<TemplateEdgeDef>
{
new() { FromTempId = "outline", ToTempId = "words" },
new() { FromTempId = "words", ToTempId = "draft" },
new() { FromTempId = "draft", ToTempId = "edit" },
new() { FromTempId = "edit", ToTempId = "beta" },
new() { FromTempId = "beta", ToTempId = "professional" },
new() { FromTempId = "professional", ToTempId = "cover" },
new() { FromTempId = "professional", ToTempId = "publish" }
}
}
}
};
public static IEnumerable<TemplateInfoDto> GetTemplateInfos()
{
var list = new List<TemplateInfoDto>();
foreach (var kv in Templates)
{
list.Add(new TemplateInfoDto
{
Key = kv.Value.Key,
Title = kv.Value.Title,
Description = kv.Value.Description,
Icon = kv.Value.Icon,
Category = kv.Value.Category
});
}
return list;
}
public static (List<TaskNode> Nodes, List<Edge> Edges) GetTemplateContent(string key, Guid graphId)
{
if (!Templates.TryGetValue(key.ToLower().Trim(), out var template))
{
return (new List<TaskNode>(), new List<Edge>());
}
var nodes = new List<TaskNode>();
var edges = new List<Edge>();
var idMapping = new Dictionary<string, Guid>();
// 1. Instantiate nodes and map their IDs
foreach (var nodeDef in template.Nodes)
{
var newId = Guid.NewGuid();
idMapping[nodeDef.TempId] = newId;
var taskNode = new TaskNode
{
Id = newId,
GraphId = graphId,
Label = nodeDef.Label,
PosX = nodeDef.PosX,
PosY = nodeDef.PosY,
Type = nodeDef.Type,
State = NodeState.Pending,
CreatedAt = DateTime.UtcNow,
Description = nodeDef.Description,
Color = nodeDef.Color,
IsPinned = false,
Decision = string.IsNullOrEmpty(nodeDef.DecisionJson) ? null : nodeDef.DecisionJson
};
// For normal sketch nodes without custom Decision JSON, populate standard default JSON
if (nodeDef.Type == "sketch" && string.IsNullOrEmpty(nodeDef.DecisionJson))
{
taskNode.Decision = $"{{\"description\":\"{nodeDef.Description}\",\"progress\":0,\"showProgress\":false,\"decision\":\"bottom\",\"assignees\":[],\"color\":\"{nodeDef.Color}\",\"isPinned\":false,\"tags\":[],\"status\":\"Todo\"}}";
}
nodes.Add(taskNode);
}
// 2. Instantiate edges
foreach (var edgeDef in template.Edges)
{
if (idMapping.TryGetValue(edgeDef.FromTempId, out var fromId) &&
idMapping.TryGetValue(edgeDef.ToTempId, out var toId))
{
var edge = new Edge
{
Id = Guid.NewGuid(),
GraphId = graphId,
FromNodeId = fromId,
ToNodeId = toId,
Condition = edgeDef.Condition
};
edges.Add(edge);
}
}
return (nodes, edges);
}
}
}