diff --git a/.gitignore b/.gitignore index 8569c5b..ab1d496 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,26 @@ -# Python artifacts +# Python __pycache__/ *.py[cod] *$py.class *.so .Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments .env .venv env/ @@ -12,22 +29,26 @@ ENV/ env.bak/ venv.bak/ -# IDE artifacts +# IDE .vscode/ .idea/ *.swp *.swo *~ -# OS artifacts +# OS .DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db Thumbs.db +# Application specific +database/workflows.db +database/workflows.db-* +*.log + +# Temporary files +*.tmp +*.temp + # Development artifacts *.log *.tmp @@ -53,16 +74,10 @@ workflow_backups/ *.db *.sqlite *.sqlite3 -workflows.db # Rename logs workflow_rename_log.json # Node.js artifacts (if using npm) node_modules/ -package-lock.json - -# Python package files -*.egg-info/ -dist/ -build/ \ No newline at end of file +package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index ea38161..dbdd50e 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,22 @@ A professionally organized collection of **2,053 n8n workflows** with a lightnin pip install -r requirements.txt # Start the fast API server -python3 api_server.py +python run.py # Open in browser http://localhost:8000 ``` **Features:** -- ⚡ **Sub-100ms response times** (vs 10+ seconds before) -- 🔍 **Instant full-text search** with ranking and filters +- ⚡ **Sub-100ms response times** with SQLite FTS5 search +- 🔍 **Instant full-text search** with advanced filtering - 📱 **Responsive design** - works perfectly on mobile - 🌙 **Dark/light themes** with system preference detection -- 📊 **Live statistics** and workflow insights +- 📊 **Live statistics** - 365 unique integrations, 29,445 total nodes - 🎯 **Smart categorization** by trigger type and complexity - 📄 **On-demand JSON viewing** and download - 🔗 **Mermaid diagram generation** for workflow visualization +- 🔄 **Real-time workflow naming** with intelligent formatting ### Performance Comparison @@ -44,15 +45,16 @@ http://localhost:8000 ### Workflow Collection - **2,053 workflows** with meaningful, searchable names -- **Professional naming convention** - `[ID]_[Service]_[Purpose]_[Trigger].json` -- **Comprehensive coverage** - 100+ services and use cases +- **365 unique integrations** across popular platforms +- **29,445 total nodes** with professional categorization - **Quality assurance** - All workflows analyzed and categorized -### Recent Improvements -- ✅ **858 generic workflows renamed** from meaningless "workflow_XXX" patterns -- ✅ **36 overly long names shortened** while preserving meaning -- ✅ **9 broken filenames fixed** with proper extensions -- ✅ **100% success rate** with zero data loss during transformation +### Advanced Naming System ✨ +Our intelligent naming system converts technical filenames into readable titles: +- **Before**: `2051_Telegram_Webhook_Automation_Webhook.json` +- **After**: `Telegram Webhook Automation` +- **100% meaningful names** with smart capitalization +- **Automatic integration detection** from node analysis --- @@ -60,23 +62,29 @@ http://localhost:8000 ### Option 1: Modern Fast System (Recommended) ```bash +# Clone repository +git clone +cd n8n-workflows + # Install Python dependencies -pip install fastapi uvicorn +pip install -r requirements.txt # Start the documentation server -python3 api_server.py +python run.py # Browse workflows at http://localhost:8000 -# - Instant search and filtering +# - Instant search across 2,053 workflows # - Professional responsive interface # - Real-time workflow statistics ``` -### Option 2: Legacy System (Deprecated) +### Option 2: Development Mode ```bash -# ⚠️ WARNING: Generates 71MB file, very slow -python3 generate_documentation.py -# Then open workflow-documentation.html +# Start with auto-reload for development +python api_server.py --reload + +# Or specify custom host/port +python api_server.py --host 0.0.0.0 --port 3000 ``` ### Import Workflows into n8n @@ -85,84 +93,95 @@ python3 generate_documentation.py 3. Choose any `.json` file from the `workflows/` folder 4. Update credentials/webhook URLs before running -### Bulk Import All Workflows -```bash -./import-workflows.sh -``` - --- ## 📊 Workflow Statistics +### Current Collection Stats - **Total Workflows**: 2,053 automation workflows -- **Naming Quality**: 100% meaningful names (improved from 58%) -- **Categories**: Data sync, notifications, integrations, monitoring -- **Services**: 100+ platforms (Gmail, Slack, Notion, Stripe, etc.) -- **Complexity Range**: Simple 2-node to complex 50+ node automations -- **File Format**: Standard n8n-compatible JSON exports +- **Active Workflows**: 215 (10.5% active rate) +- **Total Nodes**: 29,445 (avg 14.3 nodes per workflow) +- **Unique Integrations**: 365 different services and APIs +- **Database**: SQLite with FTS5 full-text search ### Trigger Distribution -- **Manual**: ~40% - User-initiated workflows -- **Webhook**: ~25% - API-triggered automations -- **Scheduled**: ~20% - Time-based executions -- **Complex**: ~15% - Multi-trigger systems +- **Complex**: 831 workflows (40.5%) - Multi-trigger systems +- **Webhook**: 519 workflows (25.3%) - API-triggered automations +- **Manual**: 477 workflows (23.2%) - User-initiated workflows +- **Scheduled**: 226 workflows (11.0%) - Time-based executions -### Complexity Levels -- **Low (≤5 nodes)**: ~45% - Simple automations -- **Medium (6-15 nodes)**: ~35% - Standard workflows -- **High (16+ nodes)**: ~20% - Complex systems +### Complexity Analysis +- **Low (≤5 nodes)**: ~35% - Simple automations +- **Medium (6-15 nodes)**: ~45% - Standard workflows +- **High (16+ nodes)**: ~20% - Complex enterprise systems + +### Popular Integrations +Top services by usage frequency: +- **Communication**: Telegram, Discord, Slack, WhatsApp +- **Cloud Storage**: Google Drive, Google Sheets, Dropbox +- **Databases**: PostgreSQL, MySQL, MongoDB, Airtable +- **AI/ML**: OpenAI, Anthropic, Hugging Face +- **Development**: HTTP Request, Webhook, GraphQL --- -## 📋 Naming Convention +## 🔍 Advanced Search Features -### Standard Format -``` -[ID]_[Service1]_[Service2]_[Purpose]_[Trigger].json -``` +### Smart Search Categories +Our system automatically categorizes workflows into 12 service categories: -### Examples +#### Available Categories: +- **messaging**: Telegram, Discord, Slack, WhatsApp, Teams +- **ai_ml**: OpenAI, Anthropic, Hugging Face +- **database**: PostgreSQL, MySQL, MongoDB, Redis, Airtable +- **email**: Gmail, Mailjet, Outlook, SMTP/IMAP +- **cloud_storage**: Google Drive, Google Docs, Dropbox, OneDrive +- **project_management**: Jira, GitHub, GitLab, Trello, Asana +- **social_media**: LinkedIn, Twitter/X, Facebook, Instagram +- **ecommerce**: Shopify, Stripe, PayPal +- **analytics**: Google Analytics, Mixpanel +- **calendar_tasks**: Google Calendar, Cal.com, Calendly +- **forms**: Typeform, Google Forms, Form Triggers +- **development**: Webhook, HTTP Request, GraphQL, SSE + +### API Usage Examples ```bash -# Good naming examples: -100_Gmail_Slack_Notification_Webhook.json -250_Stripe_Hubspot_Invoice_Sync.json -375_Airtable_Data_Backup_Scheduled.json +# Search workflows by text +curl "http://localhost:8000/api/workflows?q=telegram+automation" -# Service mappings: -n8n-nodes-base.gmail → Gmail -n8n-nodes-base.slack → Slack -n8n-nodes-base.webhook → Webhook +# Filter by trigger type and complexity +curl "http://localhost:8000/api/workflows?trigger=Webhook&complexity=high" + +# Find all messaging workflows +curl "http://localhost:8000/api/workflows/category/messaging" + +# Get database statistics +curl "http://localhost:8000/api/stats" + +# Browse available categories +curl "http://localhost:8000/api/categories" ``` -### Purpose Categories -- **Create** - Creating new records/content -- **Update** - Updating existing data -- **Sync** - Synchronizing between systems -- **Send** - Sending notifications/messages -- **Monitor** - Monitoring and alerting -- **Process** - Data processing/transformation -- **Import/Export** - Data migration tasks - --- ## 🏗 Technical Architecture -### Modern Stack (New System) -- **SQLite Database** - FTS5 full-text search, indexed metadata -- **FastAPI Backend** - REST API with automatic documentation -- **Responsive Frontend** - Single-file HTML with embedded assets -- **Smart Analysis** - Automatic workflow categorization +### Modern Stack +- **SQLite Database** - FTS5 full-text search with 365 indexed integrations +- **FastAPI Backend** - RESTful API with automatic OpenAPI documentation +- **Responsive Frontend** - Modern HTML5 with embedded CSS/JavaScript +- **Smart Analysis** - Automatic workflow categorization and naming ### Key Features -- **Change Detection** - Only reprocess modified workflows -- **Background Indexing** - Non-blocking workflow analysis -- **Compressed Responses** - Gzip middleware for speed -- **Virtual Scrolling** - Handle thousands of workflows smoothly -- **Lazy Loading** - Diagrams and JSON loaded on demand +- **Change Detection** - MD5 hashing for efficient re-indexing +- **Background Processing** - Non-blocking workflow analysis +- **Compressed Responses** - Gzip middleware for optimal speed +- **Error Handling** - Graceful degradation and comprehensive logging +- **Mobile Optimization** - Touch-friendly interface design -### Database Schema +### Database Performance ```sql --- Optimized for search and filtering +-- Optimized schema for lightning-fast queries CREATE TABLE workflows ( id INTEGER PRIMARY KEY, filename TEXT UNIQUE, @@ -171,14 +190,16 @@ CREATE TABLE workflows ( trigger_type TEXT, complexity TEXT, node_count INTEGER, - integrations TEXT, -- JSON array - tags TEXT, -- JSON array - file_hash TEXT -- For change detection + integrations TEXT, -- JSON array of 365 unique services + description TEXT, + file_hash TEXT, -- MD5 for change detection + analyzed_at TIMESTAMP ); --- Full-text search capability +-- Full-text search with ranking CREATE VIRTUAL TABLE workflows_fts USING fts5( - filename, name, description, integrations, tags + filename, name, description, integrations, tags, + content='workflows', content_rowid='id' ); ``` @@ -189,6 +210,7 @@ CREATE VIRTUAL TABLE workflows_fts USING fts5( ### System Requirements - **Python 3.7+** - For running the documentation system - **Modern Browser** - Chrome, Firefox, Safari, Edge +- **50MB Storage** - For SQLite database and indexes - **n8n Instance** - For importing and running workflows ### Installation @@ -197,14 +219,13 @@ CREATE VIRTUAL TABLE workflows_fts USING fts5( git clone cd n8n-workflows -# Install dependencies (for fast system) +# Install dependencies pip install -r requirements.txt # Start documentation server -python3 api_server.py --port 8000 +python run.py -# Or use legacy system (not recommended) -python3 generate_documentation.py +# Access at http://localhost:8000 ``` ### Development Setup @@ -215,10 +236,75 @@ source .venv/bin/activate # Linux/Mac # or .venv\Scripts\activate # Windows # Install dependencies -pip install fastapi uvicorn +pip install -r requirements.txt # Run with auto-reload for development -python3 api_server.py --reload +python api_server.py --reload + +# Force database reindexing +python workflow_db.py --index --force +``` + +--- + +## 📋 Naming Convention + +### Intelligent Formatting System +Our system automatically converts technical filenames to user-friendly names: + +```bash +# Automatic transformations: +2051_Telegram_Webhook_Automation_Webhook.json → "Telegram Webhook Automation" +0250_HTTP_Discord_Import_Scheduled.json → "HTTP Discord Import Scheduled" +0966_OpenAI_Data_Processing_Manual.json → "OpenAI Data Processing Manual" +``` + +### Technical Format +``` +[ID]_[Service1]_[Service2]_[Purpose]_[Trigger].json +``` + +### Smart Capitalization Rules +- **HTTP** → HTTP (not Http) +- **API** → API (not Api) +- **webhook** → Webhook +- **automation** → Automation +- **scheduled** → Scheduled + +--- + +## 🚀 API Documentation + +### Core Endpoints +- `GET /` - Main workflow browser interface +- `GET /api/stats` - Database statistics and metrics +- `GET /api/workflows` - Search with filters and pagination +- `GET /api/workflows/{filename}` - Detailed workflow information +- `GET /api/workflows/{filename}/download` - Download workflow JSON +- `GET /api/workflows/{filename}/diagram` - Generate Mermaid diagram + +### Advanced Search +- `GET /api/workflows/category/{category}` - Search by service category +- `GET /api/categories` - List all available categories +- `GET /api/integrations` - Get integration statistics +- `POST /api/reindex` - Trigger background reindexing + +### Response Examples +```json +// GET /api/stats +{ + "total": 2053, + "active": 215, + "inactive": 1838, + "triggers": { + "Complex": 831, + "Webhook": 519, + "Manual": 477, + "Scheduled": 226 + }, + "total_nodes": 29445, + "unique_integrations": 365 +} ``` --- @@ -227,100 +313,79 @@ python3 api_server.py --reload ### Adding New Workflows 1. **Export workflow** as JSON from n8n -2. **Name descriptively** following the naming convention +2. **Name descriptively** following the established pattern 3. **Add to workflows/** directory -4. **Test the workflow** before contributing -5. **Remove sensitive data** (credentials, personal URLs) - -### Naming Guidelines -- Use clear, descriptive names -- Follow the established format: `[ID]_[Service]_[Purpose].json` -- Maximum 80 characters when possible -- Use underscores instead of spaces +4. **Remove sensitive data** (credentials, personal URLs) +5. **Run reindexing** to update the database ### Quality Standards -- ✅ Workflow must be functional +- ✅ Workflow must be functional and tested - ✅ Remove all credentials and sensitive data -- ✅ Add meaningful description in workflow name -- ✅ Test in clean n8n instance -- ✅ Follow naming convention - ---- - -## 📚 Workflow Sources - -This collection includes workflows from: -- **Official n8n.io** - Website and community forum -- **GitHub repositories** - Public community contributions -- **Blog posts & tutorials** - Real-world examples -- **User submissions** - Tested automation patterns -- **Documentation examples** - Official n8n guides +- ✅ Follow naming convention for consistency +- ✅ Verify compatibility with recent n8n versions +- ✅ Include meaningful description or comments --- ## ⚠️ Important Notes ### Security & Privacy -- **Review before use** - All workflows shared as-is -- **Update credentials** - Remove/replace API keys and tokens +- **Review before use** - All workflows shared as-is for educational purposes +- **Update credentials** - Replace API keys, tokens, and webhooks - **Test safely** - Verify in development environment first -- **Check permissions** - Ensure proper access rights +- **Check permissions** - Ensure proper access rights for integrations ### Compatibility -- **n8n Version** - Most workflows compatible with recent versions -- **Community Nodes** - Some may require additional node installations -- **API Changes** - External services may have updated their APIs -- **Dependencies** - Check required integrations before importing +- **n8n Version** - Compatible with n8n 1.0+ (most workflows) +- **Community Nodes** - Some workflows may require additional node installations +- **API Changes** - External services may have updated their APIs since creation +- **Dependencies** - Verify required integrations before importing --- -## 🎯 Quick Start Guide +## 📚 Resources & References -1. **Clone Repository** - ```bash - git clone - cd n8n-workflows - ``` +### Workflow Sources +This comprehensive collection includes workflows from: +- **Official n8n.io** - Documentation and community examples +- **GitHub repositories** - Open source community contributions +- **Blog posts & tutorials** - Real-world automation patterns +- **User submissions** - Tested and verified workflows +- **Enterprise use cases** - Business process automations -2. **Start Fast Documentation** - ```bash - pip install fastapi uvicorn - python3 api_server.py - ``` - -3. **Browse Workflows** - - Open http://localhost:8000 - - Use instant search and filters - - Explore workflow categories - -4. **Import & Use** - - Find interesting workflows - - Download JSON files - - Import into your n8n instance - - Update credentials and test +### Learn More +- [n8n Documentation](https://docs.n8n.io/) - Official documentation +- [n8n Community](https://community.n8n.io/) - Community forum and support +- [Workflow Templates](https://n8n.io/workflows/) - Official template library +- [Integration Docs](https://docs.n8n.io/integrations/) - Service-specific guides --- ## 🏆 Project Achievements ### Repository Transformation -- **903 workflows renamed** with intelligent content analysis -- **100% meaningful names** (improved from 58% well-named) -- **Professional organization** with consistent standards -- **Zero data loss** during renaming process +- **2,053 workflows** professionally organized and named +- **365 unique integrations** automatically detected and categorized +- **100% meaningful names** (improved from basic filename patterns) +- **Zero data loss** during intelligent renaming process +- **Advanced search** with 12 service categories ### Performance Revolution -- **71MB → <100KB** documentation size (700x improvement) -- **10+ seconds → <1 second** load time (10x faster) -- **Client-side → Server-side** search (infinite scalability) -- **Static → Dynamic** interface (modern user experience) +- **Sub-100ms search** with SQLite FTS5 full-text indexing +- **Instant filtering** across 29,445 workflow nodes +- **Mobile-optimized** responsive design for all devices +- **Real-time statistics** with live database queries +- **Professional interface** with modern UX principles -### Quality Improvements -- **Intelligent categorization** - Automatic trigger and complexity detection -- **Enhanced searchability** - Full-text search with ranking -- **Mobile optimization** - Responsive design for all devices -- **Professional presentation** - Clean, modern interface +### System Reliability +- **Robust error handling** with graceful degradation +- **Change detection** for efficient database updates +- **Background processing** for non-blocking operations +- **Comprehensive logging** for debugging and monitoring +- **Production-ready** with proper middleware and security --- -*This repository represents the most comprehensive and well-organized collection of n8n workflows available, with cutting-edge documentation technology that makes workflow discovery and usage a delightful experience.* \ No newline at end of file +*This repository represents the most comprehensive and well-organized collection of n8n workflows available, featuring cutting-edge search technology and professional documentation that makes workflow discovery and usage a delightful experience.* + +**🎯 Perfect for**: Developers, automation engineers, business analysts, and anyone looking to streamline their workflows with proven n8n automations. \ No newline at end of file diff --git a/README_zh-hant.md b/README_zh-hant.md deleted file mode 100644 index 37a2026..0000000 --- a/README_zh-hant.md +++ /dev/null @@ -1,217 +0,0 @@ -# 🧠 n8n 工作流程收藏集 - -本儲存庫包含從多個來源收集的 **n8n 工作流程**,包括: - -* 從 [n8n.io](https://n8n.io) 網站和社群論壇匯出的工作流程 -* 在網路上公開分享的範例(GitHub、部落格等) - -目標是為您的 n8n 專案提供有用的靈感、學習和重複使用資源。 - ---- - -## 📂 資料夾結構 - -* 每個 `.json` 檔案代表一個匯出的工作流程。 -* 檔案根據原始標題或來源命名。 -* 您可能還會發現一些已轉換為 `.json` 的 `.txt` 檔案(請參見下方說明)。 - ---- - -## 🔄 TXT 轉 JSON 轉換 - -一些工作流程最初儲存為 `.txt` 檔案或從線上來源複製。使用腳本來: - -* 偵測 `.txt` 檔案 -* 嘗試將它們解析為 JSON 或結構化鍵值對 -* 將它們轉換為有效的 `.json` 格式 - -如果您想自己執行轉換,請查看本儲存庫中包含的 `convert_txt_to_json.py`。 - ---- - -## 🛠 使用說明 - -要將工作流程匯入您自己的 n8n 實例: - -1. 開啟您的 [n8n 編輯器 UI](https://docs.n8n.io/hosting/editor-ui/) -2. 點擊右上角的**選單** (☰) → `匯入工作流程` -3. 從此資料夾選擇一個 `.json` 檔案 -4. 點擊「匯入」來載入工作流程 - -在執行前,請確保檢查並修改所需的憑證或 webhook URL。 - -若要一次匯入所有工作流程,請執行下列命令: - -`./import-workflows.sh` - ---- - -## 📋 工作流程清單 - -以下是所有可用的工作流程,按類別分組: - -### 🤖 AI 智能代理和聊天機器人 - -* [🤖 Telegram 文字/音訊/圖片訊息代理](workflows/🤖%20Telegram%20Messaging%20Agent%20for%20Text_Audio_Images.json) - 支援多媒體的 Telegram 智能代理 -* [🤖🧑‍💻 n8n 創作者排行榜 AI 代理](workflows/🤖🧑_💻%20AI%20Agent%20for%20Top%20n8n%20Creators%20Leaderboard%20Reporting.json) - 頂級 n8n 創作者排行榜報告 -* [🤖🧠 AI 聊天機器人 + 長期記憶 + 筆記儲存 + Telegram](workflows/🤖🧠%20AI%20Agent%20Chatbot%20+%20LONG%20TERM%20Memory%20+%20Note%20Storage%20+%20Telegram.json) - 具備長期記憶功能的聊天機器人 -* [🐋🤖 DeepSeek AI 代理 + Telegram + 長期記憶 🧠](workflows/🐋🤖%20DeepSeek%20AI%20Agent%20+%20Telegram%20+%20LONG%20TERM%20Memory%20🧠.json) - 基於 DeepSeek 的智能代理 -* [🔐🦙🤖 私人本地 Ollama 自主 AI 助理](workflows/🔐🦙🤖%20Private%20&%20Local%20Ollama%20Self-Hosted%20AI%20Assistant.json) - 本地部署的私人 AI 助理 -* [🔥📈🤖 n8n 創作者排行榜 AI 代理 - 尋找熱門工作流程](workflows/🔥📈🤖%20AI%20Agent%20for%20n8n%20Creators%20Leaderboard%20-%20Find%20Popular%20Workflows.json) - 發現熱門工作流程 -* [🚀 本地多 LLM 測試與效能追蹤器](workflows/🚀%20Local%20Multi-LLM%20Testing%20&%20Performance%20Tracker.json) - 多模型效能比較工具 -* [HR & IT 服務台聊天機器人與音訊轉錄](workflows/zmgSshZ5xESr3ozl_HR_&_IT_Helpdesk_Chatbot_with_Audio_Transcription.json) - 企業服務台解決方案 -* [旅遊助理代理](workflows/znRwva47HzXesOYk_Travel_AssistantAgent.json) - 智能旅遊規劃助手 -* [🐋DeepSeek V3 聊天與 R1 推理快速開始](workflows/🐋DeepSeek%20V3%20Chat%20&%20R1%20Reasoning%20Quick%20Start.json) - DeepSeek V3 模型使用指南 -* [使用 LangChain 和 Gemini 建構自訂 AI 代理(自主託管)](workflows/yCIEiv9QUHP8pNfR_Build_Custom_AI_Agent_with_LangChain_&_Gemini_(Self-Hosted).json) - 自訂 AI 代理建構指南 -* [使用 Jina.ai 網頁爬蟲的 AI 代理聊天機器人](workflows/xEij0kj2I1DHbL3I_🌐🪛_AI_Agent_Chatbot_with_Jina.ai_Webpage_Scraper.json) - 網頁內容理解聊天機器人 -* [WhatsApp 文字、語音、圖片和 PDF AI 聊天機器人](workflows/zMtPPjJ80JJznrJP_AI-Powered_WhatsApp_Chatbot_for_Text,_Voice,_Images_&_PDFs.json) - 全功能 WhatsApp 助手 -* [Line 聊天機器人使用 Groq 和 Llama3 處理 AI 回應](workflows/xibc6WDU53isYN1o_Line_Chatbot_Handling_AI_Responses_with_Groq_and_Llama3.json) - Line 平台聊天機器人 -* [Microsoft Outlook AI 電子郵件助理](workflows/reQhibpNwU63Y8sn_Microsoft_Outlook_AI_Email_Assistant.json) - 智能郵件處理助手 -* [電子郵件 AI 自動回覆器。總結並發送電子郵件](workflows/q8IFGLeOCGSfoWZu_Email_AI_Auto-responder._Summerize_and_send_email.json) - 智能郵件自動回覆 -* [Discord MCP 聊天代理](workflows/xRclXA5QzrT3c6U8_Discord_MCP_Chat_Agent.json) - Discord 聊天機器人 -* [Hubspot 聊天助理使用 OpenAI 和 Airtable](workflows/vAssistant%20for%20Hubspot%20Chat%20using%20OpenAi%20and%20Airtable.json) - CRM 整合聊天助手 - -### 📊 內容創作與分析 - -* [📚 使用 GPT 和 Docsify 自動生成 n8n 工作流程文件](workflows/📚%20Auto-generate%20documentation%20for%20n8n%20workflows%20with%20GPT%20and%20Docsify.json) - 自動文件生成 -* [🔍 Perplexity 研究轉 HTML:AI 驅動的內容創作](workflows/🔍%20Perplexity%20Research%20to%20HTML_%20AI-Powered%20Content%20Creation.json) - 研究內容轉換 -* [⚡AI 驅動的 YouTube 影片摘要與分析](workflows/⚡AI-Powered%20YouTube%20Video%20Summarization%20&%20Analysis.json) - 影片內容分析 -* [🎨 使用 FLUX.1 填充工具的互動式圖片編輯器](workflows/🎨%20Interactive%20Image%20Editor%20with%20FLUX.1%20Fill%20Tool%20for%20Inpainting.json) - AI 圖片編輯 -* [將 YouTube 影片轉換為摘要、轉錄和視覺洞察](workflows/wZBgoWrBZveMmzYi_Turn_YouTube_Videos_into_Summaries,_Transcripts,_and_Visual_Insights.json) - 影片內容分析 -* [YouTube 評論情感分析器](workflows/xaC6zL4bWBo14xyJ_YouTube_Comment_Sentiment_Analyzer.json) - 評論情感分析 -* [WordPress 部落格文章 AI 自動標記](workflows/siXUnQhJpCJ9rHzu_Auto-Tag_Blog_Posts_in_WordPress_with_AI.json) - 內容標記自動化 -* [🎦💌進階 YouTube RSS 訂閱好幫手](workflows/tHgDFmFyuj6DnP6l_🎦💌Advanced_YouTube_RSS_Feed_Buddy_for_Your_Favorite_Channels.json) - YouTube 內容追蹤 -* [n8n 圖形設計團隊](workflows/tnRYt0kDGMO9BBFd_n8n_Graphic_Design_Team.json) - 設計工作流程 -* [使用 Perplexity AI 搜尋新聞並發布到 X (Twitter)](workflows/v9K61fCQhrG6gt6Z_Search_news_using_Perplexity_AI_and_post_to_X_(Twitter).json) - 新聞分享自動化 -* [社群媒體發布器](workflows/r1u4HOJu5j5sP27x_Social_Media_Publisher.json) - 多平台內容發布 -* [發布到 X](workflows/plzObaqgoEvV4UU0_Post_on_X.json) - Twitter 自動發布 -* [YouTube 自動化](workflows/wLbJ7rE6vQzizCp2_Youtube_Automation.json) - YouTube 內容管理 - -### 🌐 網頁爬蟲與資料擷取 - -* [✨ 視覺基礎 AI 代理爬蟲 - 搭配 Google Sheets、ScrapingBee 和 Gemini](workflows/✨%20Vision-Based%20AI%20Agent%20Scraper%20-%20with%20Google%20Sheets,%20ScrapingBee,%20and%20Gemini.json) - 視覺化網頁爬蟲 -* [智能網頁查詢和語意重新排序流程](workflows/wa2uEnSIowqSrHoY_Intelligent_Web_Query_and_Semantic_Re-Ranking_Flow.json) - 智能搜尋排序 -* [使用 Jina.ai 的重要多頁網站爬蟲](workflows/xEij0kj2I1DHbL3I_💡🌐_Essential_Multipage_Website_Scraper_with_Jina.ai.json) - 多頁面爬蟲 -* [使用 DeepSeek 爬取 Trustpilot 評論,使用 OpenAI 分析情感](workflows/w434EiZ2z7klQAyp_Scrape_Trustpilot_Reviews_with_DeepSeek,_Analyze_Sentiment_with_OpenAI.json) - 評論情感分析 -* [使用 Bright Data 和 Gemini AI 擷取和總結維基百科資料](workflows/sczRNO4u1HYc5YV7_Extract_&_Summarize_Wikipedia_Data_with_Bright_Data_and_Gemini_AI.json) - 維基百科內容處理 -* [使用 Bright Data 和 Google Gemini 從 LinkedIn 生成公司故事](workflows/q1DorytEoEw1QLGj_Generate_Company_Stories_from_LinkedIn_with_Bright_Data_&_Google_Gemini.json) - LinkedIn 內容分析 -* [使用 Bright Data 進行品牌內容擷取、總結和情感分析](workflows/wTI77cpLkbxsRQat_Brand_Content_Extract,_Summarize_&_Sentiment_Analysis_with_Bright_Data.json) - 品牌內容分析 -* [新聞擷取](workflows/xM8Z5vZVNTNjCySL_News_Extraction.json) - 新聞內容爬蟲 - -### 📱 通訊與訊息處理 - -* [WhatsApp 入門工作流程](workflows/yxv7OYbDEnqsqfa9_WhatsApp_starter_workflow.json) - WhatsApp 基礎設定 -* [📈 從 FT.com 接收每日市場新聞到 Microsoft Outlook 收件匣](workflows/📈%20Receive%20Daily%20Market%20News%20from%20FT.com%20to%20your%20Microsoft%20outlook%20inbox.json) - 市場新聞推送 - -### 🗃️ 資料管理與同步 - -* [📦 新電子郵件 ➔ 建立 Google 任務](workflows/z0C6H2kYSgML2dib_📦_New_Email_➔_Create_Google_Task.json) - 郵件任務轉換 -* [同步 Google Sheets 與 Postgres](workflows/wDD4XugmHIvx3KMT_Synchronize_your_Google_Sheets_with_Postgres.json) - 資料庫同步 -* [從 Google Drive 同步新檔案到 Airtable](workflows/uLHpFu2ndN6ZKClZ_Sync_New_Files_From_Google_Drive_with_Airtable.json) - 檔案管理同步 -* [同步 YouTube 影片 URL 到 Google Sheets](workflows/rJNvM4vU6SLUeC1d_Sync_Youtube_Video_Urls_with_Google_Sheets.json) - 影片清單管理 -* [從 URL 匯入 CSV 到 Excel](workflows/xcl8D1sukz9Rak69_Import_CSV_from_URL_to_Excel.json) - 資料匯入 -* [自動將 CSV 檔案匯入 postgres](workflows/q8GNbRhjQDwDpXoo_How_to_automatically_import_CSV_files_into_postgres.json) - 資料庫匯入 -* [從 Google Sheets 匯入多個製造商到 Shopware 6](workflows/xLjE4IkQXARXOCZy_Import_multiple_Manufacturers_from_Google_Sheets_to_Shopware_6.json) - 電商資料匯入 -* [匯入多個 CSV 到 Google Sheet](workflows/zic2ZEHvxHR4UAYI_Import_multiple_CSV_to_GoogleSheet.json) - 批次資料匯入 -* [透過 Excel 更新角色](workflows/xzKlhjcc6QEzA98Z_Update_Roles_by_Excel.json) - 權限管理 -* [壓縮多個檔案](workflows/r3qHlCVCczqTw3pP_Zip_multiple_files.json) - 檔案打包 -* [合併多個執行成一個](workflows/ynTqojfUnGpG2rBP_Merge_multiple_runs_into_one.json) - 執行結果合併 - -### 🏢 企業與 CRM 管理 - -* [LinkedIn 自動化](workflows/yF1HNe2ucaE81fNl_Linkedin_Automation.json) - LinkedIn 行銷自動化 -* [使用 Icypeas 執行電子郵件搜尋(單次)](workflows/zAkPoRdcG5M5x4KT_Perform_an_email_search_with_Icypeas_(single).json) - 電子郵件查找 -* [會議預訂 - 到通訊和 CRM](workflows/xe9sXQUc7yW8P8im_Meeting_booked_-_to_newsletter_and_CRM.json) - 會議管理整合 -* [ICP 公司評分](workflows/xyLfWaqdIoZmbTfv_ICP_Company_Scoring.json) - 潛在客戶評分 -* [ProspectLens 公司研究](workflows/wwvUsosYUyMfpGbB_ProspectLens_company_research.json) - 客戶研究 -* [HR 重點自動化流程與 AI](workflows/t1P14FvfibKYCh3E_HR-focused_automation_pipeline_with_AI.json) - 人力資源自動化 -* [CV 評估 - 錯誤處理](workflows/vnhhf9aNsw0kzdBV_CV_Evaluation_-_Error_Handling.json) - 履歷評估系統 -* [使用 AI 發現職場歧視模式](workflows/vzU9QRZsHcyRsord_Spot_Workplace_Discrimination_Patterns_with_AI.json) - 職場分析工具 - -### 🔧 開發與維運工具 - -* [在 n8n 實例之間複製工作流程使用 n8n API](workflows/yOhH9SGiZgZTDUB4_Clone_n8n_Workflows_between_Instances_using_n8n_API.json) - 工作流程遷移 -* [憑證轉移](workflows/tlnJNm9t5H3VLU5K_Credentials_Transfer.json) - 憑證管理 -* [[OPS] 從 GitHub 恢復工作流程到 n8n](workflows/uoBZx3eMvLMxlHCS_[OPS]_Restore_workflows_from_GitHub_to_n8n.json) - 工作流程備份恢復 -* [工作流程節點更新檢查範本的附加元件](workflows/xlMrGt0c1eFi4J1U_Addon_for_Workflow_Nodes_Update_Check_Template.json) - 更新檢查工具 -* [尋找受影響表達式的參數助手](workflows/zlHbtHIcCZ9enKwg_v1_helper_-_Find_params_with_affected_expressions.json) - 除錯工具 -* [在 n8n 中測試 Webhooks 而不更改 WEBHOOK URL](workflows/sB6dC0GZ7zZHuMGF_Test_Webhooks_in_n8n_Without_Changing_WEBHOOK_URL_(PostBin_&_BambooHR_Example).json) - Webhook 測試 -* [失敗重試除了已知錯誤範本](workflows/qAzZekQuABuH8uho_Retry_on_fail_except_for_known_error_Template.json) - 錯誤處理 -* [網頁伺服器監控](workflows/pcLi17oUJK9pSaee_Web_Server_Monitor..json) - 系統監控 -* [可疑登入偵測](workflows/xQHiKDTkezDY5lFu_Suspicious_login_detection.json) - 安全監控 -* [MAIA - 健康檢查](workflows/wng5xcxlYA6jFS6n_MAIA_-_Health_Check.json) - 系統健康監控 -* [追蹤工作時間和休息](workflows/pdgNdag49lwoTxUP_Track_Working_Time_and_Pauses.json) - 時間管理 -* [基於位置觸發的自動化工作考勤](workflows/x2kgOnBLtqAjqUVS_Automated_Work_Attendance_with_Location_Triggers.json) - 考勤系統 - -### 🔌 API 與整合服務 - -* [OIDC 客戶端工作流程](workflows/zeyTmqqmXaQIFWzV_OIDC_client_workflow.json) - 身份驗證整合 -* [使用 HttpRequest 節點透過 XMLRPC 發布到 Wordpress.com](workflows/yPIST7l13huQEjY5_Use_XMLRPC_via_HttpRequest-node_to_post_on_Wordpress.com.json) - WordPress 整合 -* [圖片生成 API](workflows/wDD4XugmHIvx3KMT_Image_Generation_API.json) - 圖片生成服務 -* [使用 Kling API 為服裝生成 360° 虛擬試穿影片](workflows/xQ0xqhNzFeEdBpFK_Generate_360°_Virtual_Try-on_Videos_for_Clothing_with_Kling_API.json) - 虛擬試穿 -* [使用 Google 腳本上傳影片到雲端硬碟](workflows/wGv0NPBA0QLp4rQ6_Upload_video_to_drive_via_google_script.json) - 檔案上傳 -* [反應 PDFMonkey 回調](workflows/s6nTFZfg6xjWyJRX_React_to_PDFMonkey_Callback.json) - PDF 處理回調 -* [上傳發布圖片](workflows/ra8MrqshnzXPy55O_upload-post_images.json) - 圖片上傳 -* [Luma AI - Webhook 回應 v1 - AK](workflows/rYuhIChQyjpGNvuR_Luma_AI_-_Webhook_Response_v1_-_AK.json) - AI 服務整合 - -### 📈 分析與報告 - -* [OpenSea 分析代理工具](workflows/yRMCUm6oJEMknhbw_OpenSea_Analytics_Agent_Tool.json) - NFT 市場分析 -* [OpenSea AI 驅動的 Telegram 洞察](workflows/wi2ZWKN9XPR0jkvn_OpenSea_AI-Powered_Insights_via_Telegram.json) - NFT 市場智能分析 -* [SERPBear 分析範本](workflows/qmmXKcpJOCm9qaCk_SERPBear_analytics_template.json) - SEO 分析 -* [Google Maps 完整版](workflows/qhZvZVCoV3HLjRkq_Google_Maps_FULL.json) - 地圖服務整合 -* [擷取 Squarespace 部落格和活動集合到 Google Sheets](workflows/sUGieRWulZJ7scll_Fetch_Squarespace_Blog_&_Event_Collections_to_Google_Sheets__.json) - 內容分析 - -### 🎯 專業領域應用 - -* [使用 Gmail 和 Mailjet 將 Netflix 電子郵件轉發到多個電子郵件地址](workflows/pkw1vY5q1p2nNfNC_Forward_Netflix_emails_to_multiple_email_addresses_with_GMail_and_Mailjet.json) - 郵件轉發 -* [Namesilo 批次域名可用性檢查 [範本]](workflows/phqg5Kk3YowxoMHQ_Namesilo_Bulk_Domain_Availability_[Template].json) - 域名檢查 -* [HDW Lead Geländewagen](workflows/piapgd2e6zmzFxAq_HDW_Lead_Geländewagen.json) - 專業業務流程 -* [n8n-農產品](workflows/ziJG3tgG91Gkbina_n8n-農產品.json) - 農業應用 -* [一般 3D 簡報](workflows/vpZ1wpsniCvKYjCF_General_3D_Presentation.json) - 3D 內容處理 -* [翻譯](workflows/vssVsRO0FW6InbaY_Translate.json) - 多語言翻譯 -* [puq-docker-immich-deploy](workflows/qps97Q4NEet1Pkm4_puq-docker-immich-deploy.json) - 容器部署 -* [InstaTest](workflows/qww129cm4TM9N8Ru_InstaTest.json) - 測試自動化 - -### 🔍 文件與資料處理 - -* [使用 AI 分析螢幕截圖](workflows/wDD4XugmHIvx3KMT_Analyze_Screenshots_with_AI.json) - 圖片分析 -* [使用 Vertex AI (Gemini) 從 PDF 和圖片擷取文字到 CSV](workflows/sUIPemKdKqmUQFt6_Extract_text_from_PDF_and_image_using_Vertex_AI_(Gemini)_into_CSV.json) - 文件內容擷取 -* [從 Splunk 警報建立唯一的 Jira 票證](workflows/uD31xU0VYjogxWoY_Create_Unique_Jira_tickets_from_Splunk_alerts.json) - 事件管理 - -### 🎮 其他實用工具 - -* [我的工作流程](workflows/yYjRbTWULZuNLXM0_My_workflow.json) - 個人工作流程範例 -* [我的工作流程 6](workflows/rLoXUoKSZ4a9XUAv_My_workflow_6.json) - 個人工作流程範例 6 -* [工作流程 x2VUvhqV1YTJCIN0](workflows/x2VUvhqV1YTJCIN0_workflow_x2VUvhqV1YTJCIN0.json) - 自訂工作流程範例 - ---- - -## 🤝 貢獻 - -發現了有趣的工作流程或創建了自己的工作流程? -歡迎貢獻到這個收藏集! - -請確保: - -* 使用描述性的檔案名稱 -* 如果適用,在頂部包含原始來源的簡短註釋 - ---- - -## ⚠️ 免責聲明 - -此處的所有工作流程都是**按原樣**分享。 -在生產環境中使用之前,請務必在安全環境中檢查和測試它們。 - ---- - -## 📊 統計資訊 - -* **總工作流程數量**:107 個 -* **主要類別**:AI 智能代理、內容創作、資料管理、企業應用、開發工具 -* **支援的服務**:Telegram、WhatsApp、OpenAI、Google Sheets、n8n API、WordPress 等 - ---- - -*最後更新:2024年12月* \ No newline at end of file diff --git a/api_server.py b/api_server.py index 564b9ed..1a5e1a8 100644 --- a/api_server.py +++ b/api_server.py @@ -39,6 +39,20 @@ app.add_middleware( # Initialize database db = WorkflowDatabase() +# Startup function to verify database +@app.on_event("startup") +async def startup_event(): + """Verify database connectivity on startup.""" + try: + stats = db.get_stats() + if stats['total'] == 0: + print("⚠️ Warning: No workflows found in database. Run indexing first.") + else: + print(f"✅ Database connected: {stats['total']} workflows indexed") + except Exception as e: + print(f"❌ Database connection failed: {e}") + raise + # Response models class WorkflowSummary(BaseModel): id: Optional[int] = None @@ -345,6 +359,68 @@ async def get_integrations(): except Exception as e: raise HTTPException(status_code=500, detail=f"Error fetching integrations: {str(e)}") +@app.get("/api/categories") +async def get_categories(): + """Get available service categories for filtering.""" + try: + categories = db.get_service_categories() + return {"categories": categories} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error fetching categories: {str(e)}") + +@app.get("/api/workflows/category/{category}", response_model=SearchResponse) +async def search_workflows_by_category( + category: str, + page: int = Query(1, ge=1, description="Page number"), + per_page: int = Query(20, ge=1, le=100, description="Items per page") +): + """Search workflows by service category (messaging, database, ai_ml, etc.).""" + try: + offset = (page - 1) * per_page + + workflows, total = db.search_by_category( + category=category, + limit=per_page, + offset=offset + ) + + # Convert to Pydantic models with error handling + workflow_summaries = [] + for workflow in workflows: + try: + clean_workflow = { + 'id': workflow.get('id'), + 'filename': workflow.get('filename', ''), + 'name': workflow.get('name', ''), + 'active': workflow.get('active', False), + 'description': workflow.get('description', ''), + 'trigger_type': workflow.get('trigger_type', 'Manual'), + 'complexity': workflow.get('complexity', 'low'), + 'node_count': workflow.get('node_count', 0), + 'integrations': workflow.get('integrations', []), + 'tags': workflow.get('tags', []), + 'created_at': workflow.get('created_at'), + 'updated_at': workflow.get('updated_at') + } + workflow_summaries.append(WorkflowSummary(**clean_workflow)) + except Exception as e: + print(f"Error converting workflow {workflow.get('filename', 'unknown')}: {e}") + continue + + pages = (total + per_page - 1) // per_page + + return SearchResponse( + workflows=workflow_summaries, + total=total, + page=page, + per_page=per_page, + pages=pages, + query=f"category:{category}", + filters={"category": category} + ) + except Exception as e: + raise HTTPException(status_code=500, detail=f"Error searching by category: {str(e)}") + # Custom exception handler for better error responses @app.exception_handler(Exception) async def global_exception_handler(request, exc): diff --git a/cleanup_database.py b/cleanup_database.py deleted file mode 100644 index 93d4e2f..0000000 --- a/cleanup_database.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to clean up the database by removing orphaned workflows -(workflows that exist in database but not on filesystem) -""" - -import os -import sqlite3 -from pathlib import Path - -def cleanup_orphaned_workflows(): - """Remove workflow entries from database that don't have corresponding files.""" - - # Connect to database - db_path = "workflows.db" - if not os.path.exists(db_path): - print("❌ Database not found. Run the API server first to create the database.") - return - - conn = sqlite3.connect(db_path) - cursor = conn.cursor() - - try: - # Get all workflow filenames from database - cursor.execute("SELECT filename FROM workflows") - db_workflows = [row[0] for row in cursor.fetchall()] - - # Get all actual workflow files from filesystem - workflows_dir = Path("workflows") - if not workflows_dir.exists(): - print("❌ Workflows directory not found.") - return - - actual_files = set() - for file_path in workflows_dir.glob("*.json"): - actual_files.add(file_path.name) - - # Find orphaned workflows (in database but not on filesystem) - orphaned = [] - for db_filename in db_workflows: - if db_filename not in actual_files: - orphaned.append(db_filename) - - if not orphaned: - print("✅ No orphaned workflows found. Database is clean!") - return - - print(f"🧹 Found {len(orphaned)} orphaned workflows in database:") - for i, filename in enumerate(orphaned[:10], 1): # Show first 10 - print(f" {i}. {filename}") - - if len(orphaned) > 10: - print(f" ... and {len(orphaned) - 10} more") - - # Ask for confirmation - response = input(f"\n❓ Remove {len(orphaned)} orphaned workflows from database? (y/N): ") - if response.lower() not in ['y', 'yes']: - print("❌ Operation cancelled.") - return - - # Remove orphaned workflows - placeholders = ','.join(['?' for _ in orphaned]) - cursor.execute(f"DELETE FROM workflows WHERE filename IN ({placeholders})", orphaned) - - # Also remove from FTS table - cursor.execute(f"DELETE FROM workflows_fts WHERE filename IN ({placeholders})", orphaned) - - conn.commit() - print(f"✅ Removed {len(orphaned)} orphaned workflows from database.") - - # Show updated stats - cursor.execute("SELECT COUNT(*) FROM workflows") - total_count = cursor.fetchone()[0] - print(f"📊 Database now contains {total_count} workflows.") - - except Exception as e: - print(f"❌ Error cleaning database: {e}") - conn.rollback() - finally: - conn.close() - -def find_missing_workflows(): - """Find workflow files that exist on filesystem but not in database.""" - - db_path = "workflows.db" - if not os.path.exists(db_path): - print("❌ Database not found. Run the API server first to create the database.") - return - - conn = sqlite3.connect(db_path) - cursor = conn.cursor() - - try: - # Get all workflow filenames from database - cursor.execute("SELECT filename FROM workflows") - db_workflows = set(row[0] for row in cursor.fetchall()) - - # Get all actual workflow files from filesystem - workflows_dir = Path("workflows") - if not workflows_dir.exists(): - print("❌ Workflows directory not found.") - return - - actual_files = [] - for file_path in workflows_dir.glob("*.json"): - actual_files.append(file_path.name) - - # Find missing workflows (on filesystem but not in database) - missing = [] - for filename in actual_files: - if filename not in db_workflows: - missing.append(filename) - - if not missing: - print("✅ All workflow files are indexed in database!") - return - - print(f"📁 Found {len(missing)} workflow files not in database:") - for i, filename in enumerate(missing[:10], 1): # Show first 10 - print(f" {i}. {filename}") - - if len(missing) > 10: - print(f" ... and {len(missing) - 10} more") - - print(f"\n💡 Run 'curl -X POST http://localhost:8000/api/reindex?force=true' to reindex all workflows.") - - except Exception as e: - print(f"❌ Error checking for missing workflows: {e}") - finally: - conn.close() - -def show_database_stats(): - """Show current database statistics.""" - - db_path = "workflows.db" - if not os.path.exists(db_path): - print("❌ Database not found. Run the API server first to create the database.") - return - - conn = sqlite3.connect(db_path) - cursor = conn.cursor() - - try: - # Get total workflows - cursor.execute("SELECT COUNT(*) FROM workflows") - total = cursor.fetchone()[0] - - # Get active/inactive counts - cursor.execute("SELECT COUNT(*) FROM workflows WHERE active = 1") - active = cursor.fetchone()[0] - inactive = total - active - - # Get trigger type distribution - cursor.execute("SELECT trigger_type, COUNT(*) FROM workflows GROUP BY trigger_type ORDER BY COUNT(*) DESC") - triggers = cursor.fetchall() - - # Show filesystem stats - workflows_dir = Path("workflows") - if workflows_dir.exists(): - actual_files = len(list(workflows_dir.glob("*.json"))) - else: - actual_files = 0 - - print("📊 Database Statistics:") - print(f" Total workflows in DB: {total}") - print(f" Active workflows: {active}") - print(f" Inactive workflows: {inactive}") - print(f" Files on filesystem: {actual_files}") - - if total != actual_files: - print(f" ⚠️ Database/filesystem mismatch: {abs(total - actual_files)} difference") - - print("\n🎯 Trigger Types:") - for trigger_type, count in triggers: - print(f" {trigger_type}: {count}") - - except Exception as e: - print(f"❌ Error getting database stats: {e}") - finally: - conn.close() - -if __name__ == "__main__": - import sys - - if len(sys.argv) > 1: - command = sys.argv[1].lower() - if command == "cleanup": - cleanup_orphaned_workflows() - elif command == "missing": - find_missing_workflows() - elif command == "stats": - show_database_stats() - else: - print("❌ Unknown command. Use: cleanup, missing, or stats") - else: - print("🧹 Database Cleanup Tool") - print("\nAvailable commands:") - print(" python3 cleanup_database.py cleanup - Remove orphaned workflows from database") - print(" python3 cleanup_database.py missing - Find workflows missing from database") - print(" python3 cleanup_database.py stats - Show database statistics") - print("\nRunning stats by default...\n") - show_database_stats() \ No newline at end of file diff --git a/comprehensive_workflow_renamer.py b/comprehensive_workflow_renamer.py deleted file mode 100644 index eda5f59..0000000 --- a/comprehensive_workflow_renamer.py +++ /dev/null @@ -1,396 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive N8N Workflow Renamer -Complete standardization of all 2053+ workflows with uniform naming convention. -""" - -import json -import os -import glob -import re -import shutil -from typing import Dict, List, Any, Optional, Tuple -from pathlib import Path - -class ComprehensiveWorkflowRenamer: - """Renames ALL workflows to uniform 0001-9999 standard with intelligent analysis.""" - - def __init__(self, workflows_dir: str = "workflows"): - self.workflows_dir = workflows_dir - self.rename_log = [] - self.errors = [] - self.backup_dir = "workflow_backups" - - def analyze_all_workflows(self) -> Dict[str, Any]: - """Analyze all workflow files and generate comprehensive rename plan.""" - if not os.path.exists(self.workflows_dir): - print(f"❌ Workflows directory '{self.workflows_dir}' not found.") - return {'workflows': [], 'total': 0, 'errors': []} - - json_files = glob.glob(os.path.join(self.workflows_dir, "*.json")) - - if not json_files: - print(f"❌ No JSON files found in '{self.workflows_dir}' directory.") - return {'workflows': [], 'total': 0, 'errors': []} - - print(f"🔍 Analyzing {len(json_files)} workflow files...") - - workflows = [] - for file_path in json_files: - try: - workflow_data = self._analyze_workflow_file(file_path) - if workflow_data: - workflows.append(workflow_data) - except Exception as e: - error_msg = f"Error analyzing {file_path}: {str(e)}" - print(f"❌ {error_msg}") - self.errors.append(error_msg) - continue - - # Sort by current filename for consistent numbering - workflows.sort(key=lambda x: x['current_filename']) - - # Assign new sequential numbers - for i, workflow in enumerate(workflows, 1): - workflow['new_number'] = f"{i:04d}" - workflow['new_filename'] = self._generate_new_filename(workflow, i) - - return { - 'workflows': workflows, - 'total': len(workflows), - 'errors': self.errors - } - - def _analyze_workflow_file(self, file_path: str) -> Optional[Dict[str, Any]]: - """Analyze a single workflow file and extract metadata for renaming.""" - try: - with open(file_path, 'r', encoding='utf-8') as f: - data = json.load(f) - except (json.JSONDecodeError, UnicodeDecodeError) as e: - print(f"❌ Error reading {file_path}: {str(e)}") - return None - - filename = os.path.basename(file_path) - - # Extract workflow metadata - workflow = { - 'current_filename': filename, - 'current_path': file_path, - 'name': data.get('name', filename.replace('.json', '')), - 'workflow_id': data.get('id', ''), - 'active': data.get('active', False), - 'nodes': data.get('nodes', []), - 'connections': data.get('connections', {}), - 'tags': data.get('tags', []), - 'created_at': data.get('createdAt', ''), - 'updated_at': data.get('updatedAt', '') - } - - # Analyze nodes for intelligent naming - node_count = len(workflow['nodes']) - workflow['node_count'] = node_count - - # Determine complexity - if node_count <= 5: - complexity = 'Simple' - elif node_count <= 15: - complexity = 'Standard' - else: - complexity = 'Complex' - workflow['complexity'] = complexity - - # Find services and trigger type - services, trigger_type = self._analyze_nodes(workflow['nodes']) - workflow['services'] = list(services) - workflow['trigger_type'] = trigger_type - - # Determine purpose from name and nodes - workflow['purpose'] = self._determine_purpose(workflow['name'], workflow['nodes']) - - return workflow - - def _analyze_nodes(self, nodes: List[Dict]) -> Tuple[set, str]: - """Analyze nodes to determine services and trigger type.""" - services = set() - trigger_type = 'Manual' - - for node in nodes: - node_type = node.get('type', '') - node_name = node.get('name', '') - - # Determine trigger type - if any(x in node_type.lower() for x in ['webhook', 'http']): - trigger_type = 'Webhook' - elif any(x in node_type.lower() for x in ['cron', 'schedule', 'interval']): - trigger_type = 'Scheduled' - elif 'trigger' in node_type.lower() and trigger_type == 'Manual': - trigger_type = 'Triggered' - - # Extract service names - if node_type.startswith('n8n-nodes-base.'): - service = node_type.replace('n8n-nodes-base.', '') - service = service.replace('Trigger', '').replace('trigger', '') - - # Clean up service names - service_mapping = { - 'webhook': 'Webhook', - 'httpRequest': 'HTTP', - 'cron': 'Cron', - 'gmail': 'Gmail', - 'slack': 'Slack', - 'googleSheets': 'GoogleSheets', - 'airtable': 'Airtable', - 'notion': 'Notion', - 'telegram': 'Telegram', - 'discord': 'Discord', - 'twitter': 'Twitter', - 'github': 'GitHub', - 'hubspot': 'HubSpot', - 'salesforce': 'Salesforce', - 'stripe': 'Stripe', - 'shopify': 'Shopify', - 'trello': 'Trello', - 'asana': 'Asana', - 'clickup': 'ClickUp', - 'calendly': 'Calendly', - 'zoom': 'Zoom', - 'mattermost': 'Mattermost', - 'microsoftTeams': 'Teams', - 'googleCalendar': 'GoogleCalendar', - 'googleDrive': 'GoogleDrive', - 'dropbox': 'Dropbox', - 'onedrive': 'OneDrive', - 'aws': 'AWS', - 'azure': 'Azure', - 'googleCloud': 'GCP' - } - - clean_service = service_mapping.get(service, service.title()) - - # Skip utility nodes - if clean_service not in ['Set', 'Function', 'If', 'Switch', 'Merge', 'StickyNote', 'NoOp']: - services.add(clean_service) - - return services, trigger_type - - def _determine_purpose(self, name: str, nodes: List[Dict]) -> str: - """Determine workflow purpose from name and node analysis.""" - name_lower = name.lower() - - # Purpose keywords mapping - purpose_keywords = { - 'create': ['create', 'add', 'new', 'generate', 'build'], - 'update': ['update', 'modify', 'change', 'edit', 'patch'], - 'sync': ['sync', 'synchronize', 'mirror', 'replicate'], - 'send': ['send', 'email', 'message', 'notify', 'alert'], - 'import': ['import', 'load', 'fetch', 'get', 'retrieve'], - 'export': ['export', 'save', 'backup', 'archive'], - 'monitor': ['monitor', 'check', 'watch', 'track', 'status'], - 'process': ['process', 'transform', 'convert', 'parse'], - 'automate': ['automate', 'workflow', 'bot', 'automation'] - } - - for purpose, keywords in purpose_keywords.items(): - if any(keyword in name_lower for keyword in keywords): - return purpose.title() - - # Default purpose based on node analysis - return 'Automation' - - def _generate_new_filename(self, workflow: Dict, number: int) -> str: - """Generate new standardized filename.""" - # Format: 0001_Service1_Service2_Purpose_Trigger.json - - services = workflow['services'][:2] # Max 2 services in filename - purpose = workflow['purpose'] - trigger = workflow['trigger_type'] - - # Build filename components - parts = [f"{number:04d}"] - - # Add services - if services: - parts.extend(services) - - # Add purpose - parts.append(purpose) - - # Add trigger if not Manual - if trigger != 'Manual': - parts.append(trigger) - - # Join and clean filename - filename = '_'.join(parts) - filename = re.sub(r'[^\w\-_]', '', filename) # Remove special chars - filename = re.sub(r'_+', '_', filename) # Collapse multiple underscores - filename = filename.strip('_') # Remove leading/trailing underscores - - return f"{filename}.json" - - def create_backup(self) -> bool: - """Create backup of current workflows directory.""" - try: - if os.path.exists(self.backup_dir): - shutil.rmtree(self.backup_dir) - - shutil.copytree(self.workflows_dir, self.backup_dir) - print(f"✅ Backup created at: {self.backup_dir}") - return True - except Exception as e: - print(f"❌ Failed to create backup: {e}") - return False - - def execute_rename_plan(self, rename_plan: Dict[str, Any], dry_run: bool = True) -> bool: - """Execute the comprehensive rename plan.""" - if not rename_plan['workflows']: - print("❌ No workflows to rename.") - return False - - print(f"\n{'🔍 DRY RUN - ' if dry_run else '🚀 EXECUTING - '}Renaming {rename_plan['total']} workflows") - - if not dry_run: - if not self.create_backup(): - print("❌ Cannot proceed without backup.") - return False - - success_count = 0 - - for workflow in rename_plan['workflows']: - old_path = workflow['current_path'] - new_filename = workflow['new_filename'] - new_path = os.path.join(self.workflows_dir, new_filename) - - # Check for filename conflicts - if os.path.exists(new_path) and old_path != new_path: - print(f"⚠️ Conflict: {new_filename} already exists") - continue - - if dry_run: - print(f"📝 {workflow['current_filename']} → {new_filename}") - else: - try: - os.rename(old_path, new_path) - self.rename_log.append({ - 'old': workflow['current_filename'], - 'new': new_filename, - 'services': workflow['services'], - 'purpose': workflow['purpose'], - 'trigger': workflow['trigger_type'] - }) - success_count += 1 - print(f"✅ {workflow['current_filename']} → {new_filename}") - except Exception as e: - error_msg = f"❌ Failed to rename {workflow['current_filename']}: {e}" - print(error_msg) - self.errors.append(error_msg) - - if not dry_run: - print(f"\n🎉 Rename complete: {success_count}/{rename_plan['total']} workflows renamed") - self._save_rename_log() - - return True - - def _save_rename_log(self): - """Save detailed rename log to file.""" - log_data = { - 'timestamp': os.popen('date').read().strip(), - 'total_renamed': len(self.rename_log), - 'errors': self.errors, - 'renames': self.rename_log - } - - with open('workflow_rename_log.json', 'w', encoding='utf-8') as f: - json.dump(log_data, f, indent=2, ensure_ascii=False) - - print(f"📄 Rename log saved to: workflow_rename_log.json") - - def generate_report(self, rename_plan: Dict[str, Any]) -> str: - """Generate comprehensive rename report.""" - workflows = rename_plan['workflows'] - total = rename_plan['total'] - - # Statistics - services_count = {} - purposes_count = {} - triggers_count = {} - - for workflow in workflows: - for service in workflow['services']: - services_count[service] = services_count.get(service, 0) + 1 - - purposes_count[workflow['purpose']] = purposes_count.get(workflow['purpose'], 0) + 1 - triggers_count[workflow['trigger_type']] = triggers_count.get(workflow['trigger_type'], 0) + 1 - - report = f""" -# 🎯 Comprehensive Workflow Rename Plan - -## 📊 Overview -- **Total workflows**: {total} -- **Naming convention**: 0001-{total:04d}_Service1_Service2_Purpose_Trigger.json -- **Quality improvement**: 100% standardized naming - -## 🏷️ Service Distribution -""" - - for service, count in sorted(services_count.items(), key=lambda x: x[1], reverse=True)[:10]: - report += f"- **{service}**: {count} workflows\n" - - report += f"\n## 🎯 Purpose Distribution\n" - for purpose, count in sorted(purposes_count.items(), key=lambda x: x[1], reverse=True): - report += f"- **{purpose}**: {count} workflows\n" - - report += f"\n## ⚡ Trigger Distribution\n" - for trigger, count in sorted(triggers_count.items(), key=lambda x: x[1], reverse=True): - report += f"- **{trigger}**: {count} workflows\n" - - report += f""" -## 📝 Naming Examples -""" - - for i, workflow in enumerate(workflows[:10]): - report += f"- `{workflow['current_filename']}` → `{workflow['new_filename']}`\n" - - if len(workflows) > 10: - report += f"... and {len(workflows) - 10} more workflows\n" - - return report - - -def main(): - """Main execution function.""" - import argparse - - parser = argparse.ArgumentParser(description='Comprehensive N8N Workflow Renamer') - parser.add_argument('--analyze', action='store_true', help='Analyze all workflows and create rename plan') - parser.add_argument('--execute', action='store_true', help='Execute the rename plan (requires --analyze first)') - parser.add_argument('--dry-run', action='store_true', help='Show rename plan without executing') - parser.add_argument('--report', action='store_true', help='Generate comprehensive report') - - args = parser.parse_args() - - renamer = ComprehensiveWorkflowRenamer() - - if args.analyze or args.dry_run or args.report: - print("🔍 Analyzing all workflows...") - rename_plan = renamer.analyze_all_workflows() - - if args.report: - report = renamer.generate_report(rename_plan) - print(report) - - if args.dry_run: - renamer.execute_rename_plan(rename_plan, dry_run=True) - - if args.execute: - confirm = input(f"\n⚠️ This will rename {rename_plan['total']} workflows. Continue? (yes/no): ") - if confirm.lower() == 'yes': - renamer.execute_rename_plan(rename_plan, dry_run=False) - else: - print("❌ Rename cancelled.") - - else: - parser.print_help() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/generate_documentation.py b/generate_documentation.py deleted file mode 100644 index b60ac3a..0000000 --- a/generate_documentation.py +++ /dev/null @@ -1,2186 +0,0 @@ -#!/usr/bin/env python3 -""" -⚠️ DEPRECATED: N8N Workflow Documentation Generator (Legacy System) - -🚨 WARNING: This script generates a 71MB HTML file that is extremely slow to load. - It has been replaced by a modern FastAPI system that's 700x smaller and 10x faster. - -🆕 USE THE NEW SYSTEM INSTEAD: - 1. pip install fastapi uvicorn - 2. python3 api_server.py - 3. Open http://localhost:8000 - -📊 PERFORMANCE COMPARISON: - Old System (this script): 71MB, 10+ seconds load time, poor mobile support - New System (api_server.py): <100KB, <1 second load time, excellent mobile support - -⚡ The new system provides: - - Instant full-text search with ranking - - Real-time filtering and statistics - - Professional responsive design - - Sub-100ms response times - - Dark/light theme support - -This legacy script is kept for backwards compatibility only. -For the best experience, please use the new FastAPI documentation system. - -Usage (NOT RECOMMENDED): python generate_documentation.py -""" - -import json -import os -import glob -import datetime -import re # Added for regex support -from typing import Dict, List, Any, Optional, Tuple, Set - -# Constants -DEFAULT_WORKFLOWS_DIR = "workflows" - - -class WorkflowAnalyzer: - """Analyzes n8n workflow JSON files and generates documentation data.""" - - def __init__(self, workflows_dir: str = DEFAULT_WORKFLOWS_DIR): - self.workflows_dir = workflows_dir - self.workflows = [] - self.stats = { - 'total': 0, - 'active': 0, - 'inactive': 0, - 'triggers': {}, - 'complexity': {'low': 0, 'medium': 0, 'high': 0}, - 'total_nodes': 0, - 'integrations': set() - } - - def analyze_all_workflows(self) -> Dict[str, Any]: - """Analyze all workflow files and return comprehensive data.""" - if not os.path.exists(self.workflows_dir): - print(f"Warning: Workflows directory '{self.workflows_dir}' not found.") - return self._get_empty_data() - - json_files = glob.glob(os.path.join(self.workflows_dir, "*.json")) - - if not json_files: - print(f"Warning: No JSON files found in '{self.workflows_dir}' directory.") - return self._get_empty_data() - - print(f"Found {len(json_files)} workflow files. Analyzing...") - - for file_path in json_files: - try: - workflow_data = self._analyze_workflow_file(file_path) - if workflow_data: - self.workflows.append(workflow_data) - except Exception as e: - print(f"Error analyzing {file_path}: {str(e)}") - continue - - self._calculate_stats() - - return { - 'workflows': self.workflows, - 'stats': self.stats, - 'timestamp': datetime.datetime.now().isoformat() - } - - def _analyze_workflow_file(self, file_path: str) -> Optional[Dict[str, Any]]: - """Analyze a single workflow file and extract metadata.""" - try: - with open(file_path, 'r', encoding='utf-8') as f: - data = json.load(f) - except (json.JSONDecodeError, UnicodeDecodeError) as e: - print(f"Error reading {file_path}: {str(e)}") - return None - - filename = os.path.basename(file_path) - - # Extract basic metadata - workflow = { - 'filename': filename, - 'name': data.get('name', filename.replace('.json', '')), - 'id': data.get('id', 'unknown'), - 'active': data.get('active', False), - 'nodes': data.get('nodes', []), - 'connections': data.get('connections', {}), - 'tags': data.get('tags', []), - 'settings': data.get('settings', {}), - 'createdAt': data.get('createdAt', ''), - 'updatedAt': data.get('updatedAt', ''), - 'versionId': data.get('versionId', '') - } - - # Analyze nodes - node_count = len(workflow['nodes']) - workflow['nodeCount'] = node_count - - # Determine complexity - if node_count <= 5: - complexity = 'low' - elif node_count <= 15: - complexity = 'medium' - else: - complexity = 'high' - workflow['complexity'] = complexity - - # Find trigger type and integrations - trigger_type, integrations = self._analyze_nodes(workflow['nodes']) - workflow['triggerType'] = trigger_type - workflow['integrations'] = list(integrations) - - # Generate description - workflow['description'] = self._generate_description(workflow, trigger_type, integrations) - # Extract or generate step-by-step process - steps = self._extract_or_generate_steps(workflow['nodes'], workflow['connections']) - workflow['steps'] = steps - - # Debug logging - if steps: - print(f"Found/Generated {len(steps)} steps in workflow: {workflow['name']}") - # Generate workflow diagram code using mermaid.js (will be rendered on-demand) - workflow['diagram'] = self._generate_workflow_diagram(workflow['nodes'], workflow['connections']) - - # Extract raw JSON for viewer - workflow['rawJson'] = json.dumps(data, indent=2) - - return workflow - - def _analyze_nodes(self, nodes: List[Dict]) -> Tuple[str, Set[str]]: - """Analyze nodes to determine trigger type and integrations.""" - trigger_type = 'Manual' - integrations = set() - - for node in nodes: - node_type = node.get('type', '') - node_name = node.get('name', '') - - # Determine trigger type - if 'webhook' in node_type.lower() or 'webhook' in node_name.lower(): - trigger_type = 'Webhook' - elif 'cron' in node_type.lower() or 'schedule' in node_type.lower(): - trigger_type = 'Scheduled' - elif 'trigger' in node_type.lower() and trigger_type == 'Manual': - if 'manual' not in node_type.lower(): - trigger_type = 'Webhook' # Most non-manual triggers are webhook-based - - # Extract integrations - if node_type.startswith('n8n-nodes-base.'): - service = node_type.replace('n8n-nodes-base.', '') - # Clean up service names - service = service.replace('Trigger', '').replace('trigger', '') - if service and service not in ['set', 'function', 'if', 'switch', 'merge', 'stickyNote']: - integrations.add(service.title()) - - # Determine if complex based on node variety and count - if len(nodes) > 10 and len(integrations) > 3: - trigger_type = 'Complex' - - return trigger_type, integrations - - def _generate_description(self, workflow: Dict, trigger_type: str, integrations: Set[str]) -> str: - """Generate a descriptive summary of the workflow.""" - name = workflow['name'] - node_count = workflow['nodeCount'] - - # Start with trigger description - trigger_descriptions = { - 'Webhook': "Webhook-triggered automation that", - 'Scheduled': "Scheduled automation that", - 'Complex': "Complex multi-step automation that", - } - desc = trigger_descriptions.get(trigger_type, "Manual workflow that") - - # Add functionality based on name and integrations - if integrations: - main_services = list(integrations)[:3] # Top 3 services - if len(main_services) == 1: - desc += f" integrates with {main_services[0]}" - elif len(main_services) == 2: - desc += f" connects {main_services[0]} and {main_services[1]}" - else: - desc += f" orchestrates {', '.join(main_services[:-1])}, and {main_services[-1]}" - - # Add workflow purpose hints from name - name_lower = name.lower() - if 'create' in name_lower: - desc += " to create new records" - elif 'update' in name_lower: - desc += " to update existing data" - elif 'sync' in name_lower: - desc += " to synchronize data" - elif 'notification' in name_lower or 'alert' in name_lower: - desc += " for notifications and alerts" - elif 'backup' in name_lower: - desc += " for data backup operations" - elif 'monitor' in name_lower: - desc += " for monitoring and reporting" - else: - desc += " for data processing" - - desc += f". Uses {node_count} nodes" - if len(integrations) > 3: - desc += f" and integrates with {len(integrations)} services" - - desc += "."; - - return desc - - def _extract_or_generate_steps(self, nodes: List[Dict], connections: Dict) -> List[Dict]: - """Extract notes from nodes or generate steps from workflow structure.""" - steps = [] - - # First, try to extract existing notes - nodes_with_notes = [] - for node in nodes: - note = node.get('notes') - if note: - nodes_with_notes.append({ - 'name': node.get('name', ''), - 'type': node.get('type', ''), - 'note': note, - 'id': node.get('id', '') - }) - - # If we have notes, use them - if nodes_with_notes: - return nodes_with_notes - - # Otherwise, generate steps from workflow structure - return self._generate_steps_from_structure(nodes, connections) - - def _generate_steps_from_structure(self, nodes: List[Dict], connections: Dict) -> List[Dict]: - """Generate step descriptions from workflow node structure and connections.""" - if not nodes: - return [] - - # Create a map of nodes by ID for easy lookup - node_map = {node.get('id', ''): node for node in nodes} - - # Find the starting node (trigger or first node) - start_node = self._find_start_node(nodes, connections) - if not start_node: - # Fallback: just describe all nodes in order - return self._generate_basic_steps(nodes) - - # Follow the workflow path - steps = [] - visited = set() - self._traverse_workflow(start_node, node_map, connections, steps, visited) - - return steps - - def _find_start_node(self, nodes: List[Dict], connections: Dict) -> Optional[Dict]: - """Find the starting node of the workflow (trigger or node with no inputs).""" - # Look for trigger nodes first - for node in nodes: - node_type = node.get('type', '').lower() - if any(trigger in node_type for trigger in ['trigger', 'webhook', 'cron', 'schedule', 'manual']): - return node - - # Find nodes that are not targets of any connections - target_nodes = set() - for source_connections in connections.values(): - if isinstance(source_connections, dict) and 'main' in source_connections: - for main_connections in source_connections['main']: - if isinstance(main_connections, list): - for connection in main_connections: - if isinstance(connection, dict) and 'node' in connection: - target_nodes.add(connection['node']) - - # Return first node that's not a target - for node in nodes: - if node.get('name', '') not in target_nodes: - return node - - # Fallback: return first node - return nodes[0] if nodes else None - - def _traverse_workflow(self, current_node: Dict, node_map: Dict, connections: Dict, steps: List[Dict], visited: set): - """Traverse the workflow following connections and generate step descriptions.""" - node_name = current_node.get('name', '') - node_id = current_node.get('id', '') - - if node_id in visited: - return - - visited.add(node_id) - - # Generate step description for current node - step_description = self._generate_step_description(current_node) - if step_description: - steps.append({ - 'name': node_name, - 'type': current_node.get('type', ''), - 'note': step_description, - 'id': node_id - }) - - # Find next nodes - if node_name in connections: - node_connections = connections[node_name] - if isinstance(node_connections, dict) and 'main' in node_connections: - for main_connections in node_connections['main']: - if isinstance(main_connections, list): - for connection in main_connections: - if isinstance(connection, dict) and 'node' in connection: - next_node_name = connection['node'] - next_node = None - for node in node_map.values(): - if node.get('name') == next_node_name: - next_node = node - break - if next_node: - self._traverse_workflow(next_node, node_map, connections, steps, visited) - - def _generate_step_description(self, node: Dict) -> str: - """Generate a meaningful description for a workflow node based on its type and parameters.""" - node_type = node.get('type', '') - node_name = node.get('name', '') - parameters = node.get('parameters', {}) - - # Clean up node type - clean_type = node_type.replace('n8n-nodes-base.', '').replace('Trigger', '').replace('trigger', '') - - # Generate description based on node type - if 'webhook' in node_type.lower(): - return f"Receives incoming webhook requests to trigger the workflow" - elif 'cron' in node_type.lower() or 'schedule' in node_type.lower(): - return f"Runs on a scheduled basis to trigger the workflow automatically" - elif 'manual' in node_type.lower(): - return f"Manual trigger to start the workflow execution" - elif 'http' in node_type.lower() or 'httpRequest' in node_type: - url = parameters.get('url', '') - method = parameters.get('method', 'GET') - return f"Makes {method} HTTP request" + (f" to {url}" if url else "") - elif 'set' in node_type.lower(): - return f"Sets and transforms data values for use in subsequent steps" - elif 'if' in node_type.lower(): - return f"Evaluates conditions to determine workflow path" - elif 'switch' in node_type.lower(): - return f"Routes workflow execution based on multiple conditions" - elif 'function' in node_type.lower() or 'code' in node_type.lower(): - return f"Executes custom JavaScript code for data processing" - elif 'merge' in node_type.lower(): - return f"Combines data from multiple workflow branches" - elif 'split' in node_type.lower(): - return f"Splits data into multiple items for parallel processing" - elif 'filter' in node_type.lower(): - return f"Filters data based on specified conditions" - elif 'gmail' in node_type.lower(): - operation = parameters.get('operation', 'send') - return f"Performs Gmail {operation} operation" - elif 'slack' in node_type.lower(): - return f"Sends message or performs action in Slack" - elif 'discord' in node_type.lower(): - return f"Sends message or performs action in Discord" - elif 'telegram' in node_type.lower(): - return f"Sends message or performs action in Telegram" - elif 'airtable' in node_type.lower(): - operation = parameters.get('operation', 'create') - return f"Performs Airtable {operation} operation on records" - elif 'google' in node_type.lower(): - if 'sheets' in node_type.lower(): - return f"Reads from or writes to Google Sheets" - elif 'drive' in node_type.lower(): - return f"Manages files in Google Drive" - elif 'calendar' in node_type.lower(): - return f"Manages Google Calendar events" - else: - return f"Integrates with Google {clean_type} service" - elif 'microsoft' in node_type.lower(): - if 'outlook' in node_type.lower(): - return f"Manages Microsoft Outlook emails" - elif 'excel' in node_type.lower(): - return f"Works with Microsoft Excel files" - else: - return f"Integrates with Microsoft {clean_type} service" - elif 'openai' in node_type.lower(): - return f"Processes data using OpenAI AI models" - elif 'anthropic' in node_type.lower(): - return f"Processes data using Anthropic Claude AI" - elif 'database' in node_type.lower() or 'mysql' in node_type.lower() or 'postgres' in node_type.lower(): - return f"Executes database operations" - elif 'wait' in node_type.lower(): - return f"Pauses workflow execution for specified duration" - elif 'error' in node_type.lower(): - return f"Handles errors and stops workflow execution" - else: - # Generic description based on service name - service_name = clean_type.title() - return f"Integrates with {service_name} to process data" - - def _generate_basic_steps(self, nodes: List[Dict]) -> List[Dict]: - """Generate basic steps when workflow structure is unclear.""" - steps = [] - for i, node in enumerate(nodes, 1): - description = self._generate_step_description(node) - if description: - steps.append({ - 'name': node.get('name', f'Step {i}'), - 'type': node.get('type', ''), - 'note': description, - 'id': node.get('id', '') - }) - return steps - - def _calculate_stats(self): - """Calculate statistics from analyzed workflows.""" - self.stats['total'] = len(self.workflows) - - for workflow in self.workflows: - # Active/inactive count - if workflow['active']: - self.stats['active'] += 1 - else: - self.stats['inactive'] += 1 - - # Trigger type count - trigger = workflow['triggerType'] - self.stats['triggers'][trigger] = self.stats['triggers'].get(trigger, 0) + 1 - - # Complexity count - complexity = workflow['complexity'] - self.stats['complexity'][complexity] += 1 - - # Node count - self.stats['total_nodes'] += workflow['nodeCount'] - - # Integrations - self.stats['integrations'].update(workflow['integrations']) - - # Convert integrations set to count - self.stats['unique_integrations'] = len(self.stats['integrations']) - self.stats['integrations'] = list(self.stats['integrations']) - - def _get_empty_data(self) -> Dict[str, Any]: - """Return empty data structure when no workflows found.""" - return { - 'workflows': [], - 'stats': { - 'total': 0, - 'active': 0, - 'inactive': 0, - 'triggers': {}, - 'complexity': {'low': 0, 'medium': 0, 'high': 0}, - 'total_nodes': 0, 'unique_integrations': 0, - 'integrations': [] - }, - 'timestamp': datetime.datetime.now().isoformat() - } - - def _generate_workflow_diagram(self, nodes: List[Dict], connections: Dict) -> str: - """ - Generate a mermaid.js workflow diagram showing node connections. - - Args: - nodes: List of workflow nodes - connections: Dictionary of workflow connections - - Returns: - str: Mermaid.js flowchart markup - """ - if not nodes: - return "graph TD\n EmptyWorkflow[No nodes found in workflow]" - - # Create mapping for node names to ensure valid mermaid IDs - mermaid_ids = {} - for i, node in enumerate(nodes): - node_id = f"node{i}" - node_name = node.get('name', f'Node {i}') - mermaid_ids[node_name] = node_id - # Start building the mermaid diagram - mermaid_code = ["graph TD"] - - # Add nodes with styling - for node in nodes: - node_name = node.get('name', 'Unnamed') - node_id = mermaid_ids[node_name] - node_type = node.get('type', '').replace('n8n-nodes-base.', '') - - # Determine node style based on type - style = "" - if any(x in node_type.lower() for x in ['trigger', 'webhook', 'cron']): - style = "fill:#b3e0ff,stroke:#0066cc" # Blue for triggers - elif any(x in node_type.lower() for x in ['if', 'switch']): - style = "fill:#ffffb3,stroke:#e6e600" # Yellow for conditional nodes - elif any(x in node_type.lower() for x in ['function', 'code']): - style = "fill:#d9b3ff,stroke:#6600cc" # Purple for code nodes - elif 'error' in node_type.lower(): - style = "fill:#ffb3b3,stroke:#cc0000" # Red for error handlers - else: - style = "fill:#d9d9d9,stroke:#666666" # Gray for other nodes - - # Add node with label (escaping special characters) - # Use HTML line break instead of \n for better compatibility - clean_name = node_name.replace('"', "'") - clean_type = node_type.replace('"', "'") - label = f"{clean_name}
({clean_type})" - mermaid_code.append(f" {node_id}[\"{label}\"]") - mermaid_code.append(f" style {node_id} {style}") - - # Add connections between nodes correctly based on n8n connection structure - for source_name, source_connections in connections.items(): - if source_name not in mermaid_ids: - continue - - if isinstance(source_connections, dict) and 'main' in source_connections: - main_connections = source_connections['main'] - - for i, output_connections in enumerate(main_connections): - if not isinstance(output_connections, list): - continue - - for connection in output_connections: - if not isinstance(connection, dict) or 'node' not in connection: - continue - - target_name = connection['node'] - if target_name not in mermaid_ids: - continue - - # Add arrow with output index if multiple outputs - label = f" -->|{i}| " if len(main_connections) > 1 else " --> " - mermaid_code.append(f" {mermaid_ids[source_name]}{label}{mermaid_ids[target_name]}") - # Format the final mermaid diagram code - return "\n".join(mermaid_code) - - -def generate_html_documentation(data: Dict[str, Any]) -> str: - """Generate the complete HTML documentation with embedded data.""" - - # Convert Python data to JavaScript with proper escaping - js_data = json.dumps(data, indent=2, ensure_ascii=False) - # Escape any script tags and HTML entities in the JSON data - js_data = js_data.replace('', '<\\/script>').replace(' - - - - - - - - - - - - - - - - - -''' - return html_template.strip() - - -def show_deprecation_warning(): - """Show deprecation warning and ask for user confirmation.""" - print("\n" + "🚨" * 20) - print("⚠️ DEPRECATED SYSTEM WARNING") - print("🚨" * 20) - print() - print("🔴 This script generates a 71MB HTML file that is extremely slow!") - print("🟢 A new FastAPI system is available that's 700x smaller and 10x faster!") - print() - print("🆕 RECOMMENDED: Use the new system instead:") - print(" 1. pip install fastapi uvicorn") - print(" 2. python3 api_server.py") - print(" 3. Open http://localhost:8000") - print() - print("📊 PERFORMANCE COMPARISON:") - print(" Old (this script): 71MB, 10+ seconds load, poor mobile") - print(" New (api_server): <100KB, <1 second load, excellent mobile") - print() - print("⚡ New system features:") - print(" ✅ Instant full-text search with ranking") - print(" ✅ Real-time filtering and statistics") - print(" ✅ Professional responsive design") - print(" ✅ Sub-100ms response times") - print(" ✅ Dark/light theme support") - print() - print("🚨" * 20) - - response = input("\nDo you still want to use this deprecated slow system? (y/N): ").strip().lower() - if response != 'y': - print("\n✅ Good choice! Please use the new FastAPI system:") - print(" python3 api_server.py") - exit(0) - - print("\n⚠️ Proceeding with deprecated system (not recommended)...") - - -def main(): - """Main function to generate the workflow documentation.""" - # Show deprecation warning first - show_deprecation_warning() - - print("\n🔍 N8N Workflow Documentation Generator (Legacy)") - print("=" * 50) - - # Initialize analyzer - analyzer = WorkflowAnalyzer() - - # Analyze workflows - data = analyzer.analyze_all_workflows() - - # Generate HTML - html = generate_html_documentation(data) - - # Write to file - output_path = "workflow-documentation.html" - with open(output_path, 'w', encoding='utf-8') as f: - f.write(html) - - print(f"✅ Documentation generated: {output_path}") - print(f" - {data['stats']['total']} workflows analyzed") - print(f" - {data['stats']['active']} active workflows") - print(f" - {data['stats']['unique_integrations']} unique integrations") - print() - print("⚠️ REMINDER: This 71MB file will be slow to load.") - print("🆕 Consider using the new FastAPI system: python3 api_server.py") - - -if __name__ == "__main__": - main() diff --git a/import-workflows.sh b/import-workflows.sh deleted file mode 100755 index 5981ff2..0000000 --- a/import-workflows.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -WORKFLOW_DIR="./workflows" - -for file in "$WORKFLOW_DIR"/*.json -do - echo "Importing $file..." - npx n8n import:workflow --input="$file" -done - diff --git a/requirements.txt b/requirements.txt index 42e7d6f..c4ea74a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -fastapi==0.104.1 -uvicorn[standard]==0.24.0 -pydantic==2.5.0 \ No newline at end of file +fastapi>=0.104.0 +uvicorn>=0.24.0 +pydantic>=2.4.0 \ No newline at end of file diff --git a/run.py b/run.py new file mode 100755 index 0000000..6caa700 --- /dev/null +++ b/run.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +🚀 Simple Launcher for n8n-workflows Search Engine +Start the system with advanced search capabilities. +""" + +import sys +import os +from pathlib import Path + +def print_banner(): + print("🚀 n8n-workflows Advanced Search Engine") + print("=" * 50) + +def check_requirements(): + """Check if requirements are installed.""" + try: + import sqlite3 + import uvicorn + import fastapi + print("✅ Dependencies verified") + return True + except ImportError as e: + print(f"❌ Missing dependency: {e}") + print("💡 Install with: pip install -r requirements.txt") + return False + +def setup_database(): + """Setup database if needed.""" + from workflow_db import WorkflowDatabase + + db_path = "database/workflows.db" + os.makedirs("database", exist_ok=True) + + print(f"🔄 Setting up database: {db_path}") + db = WorkflowDatabase(db_path) + + # Check if database has data + stats = db.get_stats() + if stats['total'] == 0: + print("📚 Indexing workflows...") + index_stats = db.index_all_workflows(force_reindex=True) + print(f"✅ Indexed {index_stats['processed']} workflows") + else: + print(f"✅ Database ready: {stats['total']} workflows") + + return db_path + +def start_server(port=8000): + """Start the API server.""" + print(f"🌐 Starting server at http://localhost:{port}") + print(f"📊 API: http://localhost:{port}/api/workflows") + print(f"🗂️ Categories: http://localhost:{port}/api/categories") + print() + print("Press Ctrl+C to stop the server") + print("-" * 50) + + # Configure database path + os.environ['WORKFLOW_DB_PATH'] = "database/workflows.db" + + # Start uvicorn without reload to avoid StatReload issues + import uvicorn + uvicorn.run("api_server:app", host="127.0.0.1", port=port, reload=False) + +def main(): + print_banner() + + # Check dependencies + if not check_requirements(): + sys.exit(1) + + # Setup database + try: + setup_database() + except Exception as e: + print(f"❌ Database setup error: {e}") + sys.exit(1) + + # Start server + try: + start_server() + except KeyboardInterrupt: + print("\n👋 Server stopped!") + except Exception as e: + print(f"❌ Server error: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/setup_fast_docs.py b/setup_fast_docs.py deleted file mode 100644 index 6533ad4..0000000 --- a/setup_fast_docs.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -""" -Setup script for the new fast N8N workflow documentation system. -""" - -import subprocess -import sys -import os -from pathlib import Path - -def run_command(command, description): - """Run a shell command and handle errors.""" - print(f"🔄 {description}...") - try: - result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True) - print(f"✅ {description} completed successfully") - return result.stdout - except subprocess.CalledProcessError as e: - print(f"❌ {description} failed: {e.stderr}") - return None - -def install_dependencies(): - """Install Python dependencies.""" - return run_command( - f"{sys.executable} -m pip install -r requirements.txt", - "Installing Python dependencies" - ) - -def index_workflows(): - """Index all workflows into the database.""" - return run_command( - f"{sys.executable} workflow_db.py --index", - "Indexing workflow files into database" - ) - -def main(): - print("🚀 Setting up N8N Fast Workflow Documentation System") - print("=" * 60) - - # Check if we're in the right directory - if not os.path.exists("workflows"): - print("❌ Error: 'workflows' directory not found. Please run this script from the repository root.") - sys.exit(1) - - # Install dependencies - if install_dependencies() is None: - print("❌ Failed to install dependencies. Please install manually:") - print(" pip install fastapi uvicorn pydantic") - sys.exit(1) - - # Index workflows - if index_workflows() is None: - print("⚠️ Warning: Failed to index workflows. You can do this manually later:") - print(" python workflow_db.py --index") - - print("\n🎉 Setup completed successfully!") - print("\n📊 Performance Comparison:") - print(" Old system: 71MB HTML file, 10s+ load time") - print(" New system: <100KB initial load, <100ms search") - print("\n🚀 To start the fast documentation server:") - print(" python api_server.py") - print("\n🌐 Then open: http://localhost:8000") - print("\n💡 Features:") - print(" • Instant search with <100ms response times") - print(" • Virtual scrolling for smooth browsing") - print(" • Real-time filtering and pagination") - print(" • Lazy-loaded diagrams and JSON viewing") - print(" • Dark/light theme support") - print(" • Mobile-responsive design") - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/workflow_db.py b/workflow_db.py index 288bf44..718d6fe 100644 --- a/workflow_db.py +++ b/workflow_db.py @@ -16,7 +16,10 @@ from pathlib import Path class WorkflowDatabase: """High-performance SQLite database for workflow metadata and search.""" - def __init__(self, db_path: str = "workflows.db"): + def __init__(self, db_path: str = None): + # Use environment variable if no path provided + if db_path is None: + db_path = os.environ.get('WORKFLOW_DB_PATH', 'workflows.db') self.db_path = db_path self.workflows_dir = "workflows" self.init_database() @@ -106,6 +109,44 @@ class WorkflowDatabase: hash_md5.update(chunk) return hash_md5.hexdigest() + def format_workflow_name(self, filename: str) -> str: + """Convert filename to readable workflow name.""" + # Remove .json extension + name = filename.replace('.json', '') + + # Split by underscores + parts = name.split('_') + + # Skip the first part if it's just a number + if len(parts) > 1 and parts[0].isdigit(): + parts = parts[1:] + + # Convert parts to title case and join with spaces + readable_parts = [] + for part in parts: + # Special handling for common terms + if part.lower() == 'http': + readable_parts.append('HTTP') + elif part.lower() == 'api': + readable_parts.append('API') + elif part.lower() == 'webhook': + readable_parts.append('Webhook') + elif part.lower() == 'automation': + readable_parts.append('Automation') + elif part.lower() == 'automate': + readable_parts.append('Automate') + elif part.lower() == 'scheduled': + readable_parts.append('Scheduled') + elif part.lower() == 'triggered': + readable_parts.append('Triggered') + elif part.lower() == 'manual': + readable_parts.append('Manual') + else: + # Capitalize first letter + readable_parts.append(part.capitalize()) + + return ' '.join(readable_parts) + def analyze_workflow_file(self, file_path: str) -> Optional[Dict[str, Any]]: """Analyze a single workflow file and extract metadata.""" try: @@ -122,7 +163,7 @@ class WorkflowDatabase: # Extract basic metadata workflow = { 'filename': filename, - 'name': data.get('name', filename.replace('.json', '')), + 'name': self.format_workflow_name(filename), 'workflow_id': data.get('id', ''), 'active': data.get('active', False), 'nodes': data.get('nodes', []), @@ -134,6 +175,12 @@ class WorkflowDatabase: 'file_size': file_size } + # Use JSON name if available and meaningful, otherwise use formatted filename + json_name = data.get('name', '').strip() + if json_name and json_name != filename.replace('.json', '') and not json_name.startswith('My workflow'): + workflow['name'] = json_name + # If no meaningful JSON name, use formatted filename (already set above) + # Analyze nodes node_count = len(workflow['nodes']) workflow['node_count'] = node_count @@ -162,12 +209,127 @@ class WorkflowDatabase: trigger_type = 'Manual' integrations = set() + # Enhanced service mapping for better recognition + service_mappings = { + # Messaging & Communication + 'telegram': 'Telegram', + 'telegramTrigger': 'Telegram', + 'discord': 'Discord', + 'slack': 'Slack', + 'whatsapp': 'WhatsApp', + 'mattermost': 'Mattermost', + 'teams': 'Microsoft Teams', + 'rocketchat': 'Rocket.Chat', + + # Email + 'gmail': 'Gmail', + 'mailjet': 'Mailjet', + 'emailreadimap': 'Email (IMAP)', + 'emailsendsmt': 'Email (SMTP)', + 'outlook': 'Outlook', + + # Cloud Storage + 'googledrive': 'Google Drive', + 'googledocs': 'Google Docs', + 'googlesheets': 'Google Sheets', + 'dropbox': 'Dropbox', + 'onedrive': 'OneDrive', + 'box': 'Box', + + # Databases + 'postgres': 'PostgreSQL', + 'mysql': 'MySQL', + 'mongodb': 'MongoDB', + 'redis': 'Redis', + 'airtable': 'Airtable', + 'notion': 'Notion', + + # Project Management + 'jira': 'Jira', + 'github': 'GitHub', + 'gitlab': 'GitLab', + 'trello': 'Trello', + 'asana': 'Asana', + 'mondaycom': 'Monday.com', + + # AI/ML Services + 'openai': 'OpenAI', + 'anthropic': 'Anthropic', + 'huggingface': 'Hugging Face', + + # Social Media + 'linkedin': 'LinkedIn', + 'twitter': 'Twitter/X', + 'facebook': 'Facebook', + 'instagram': 'Instagram', + + # E-commerce + 'shopify': 'Shopify', + 'stripe': 'Stripe', + 'paypal': 'PayPal', + + # Analytics + 'googleanalytics': 'Google Analytics', + 'mixpanel': 'Mixpanel', + + # Calendar & Tasks + 'googlecalendar': 'Google Calendar', + 'googletasks': 'Google Tasks', + 'cal': 'Cal.com', + 'calendly': 'Calendly', + + # Forms & Surveys + 'typeform': 'Typeform', + 'googleforms': 'Google Forms', + 'form': 'Form Trigger', + + # Development Tools + 'webhook': 'Webhook', + 'httpRequest': 'HTTP Request', + 'graphql': 'GraphQL', + 'sse': 'Server-Sent Events', + + # Utility nodes (exclude from integrations) + 'set': None, + 'function': None, + 'code': None, + 'if': None, + 'switch': None, + 'merge': None, + 'split': None, + 'stickynote': None, + 'stickyNote': None, + 'wait': None, + 'schedule': None, + 'cron': None, + 'manual': None, + 'stopanderror': None, + 'noop': None, + 'noOp': None, + 'error': None, + 'limit': None, + 'aggregate': None, + 'summarize': None, + 'filter': None, + 'sort': None, + 'removeDuplicates': None, + 'dateTime': None, + 'extractFromFile': None, + 'convertToFile': None, + 'readBinaryFile': None, + 'readBinaryFiles': None, + 'executionData': None, + 'executeWorkflow': None, + 'executeCommand': None, + 'respondToWebhook': None, + } + for node in nodes: node_type = node.get('type', '') - node_name = node.get('name', '') + node_name = node.get('name', '').lower() # Determine trigger type - if 'webhook' in node_type.lower() or 'webhook' in node_name.lower(): + if 'webhook' in node_type.lower() or 'webhook' in node_name: trigger_type = 'Webhook' elif 'cron' in node_type.lower() or 'schedule' in node_type.lower(): trigger_type = 'Scheduled' @@ -175,12 +337,45 @@ class WorkflowDatabase: if 'manual' not in node_type.lower(): trigger_type = 'Webhook' - # Extract integrations + # Extract integrations with enhanced mapping + service_name = None + + # Handle n8n-nodes-base nodes if node_type.startswith('n8n-nodes-base.'): - service = node_type.replace('n8n-nodes-base.', '') - service = service.replace('Trigger', '').replace('trigger', '') - if service and service not in ['set', 'function', 'if', 'switch', 'merge', 'stickyNote']: - integrations.add(service.title()) + raw_service = node_type.replace('n8n-nodes-base.', '').lower() + raw_service = raw_service.replace('trigger', '') + service_name = service_mappings.get(raw_service, raw_service.title() if raw_service else None) + + # Handle @n8n/ namespaced nodes + elif node_type.startswith('@n8n/'): + raw_service = node_type.split('.')[-1].lower() if '.' in node_type else node_type.lower() + raw_service = raw_service.replace('trigger', '') + service_name = service_mappings.get(raw_service, raw_service.title() if raw_service else None) + + # Handle custom nodes + elif '-' in node_type: + # Try to extract service name from custom node names like "n8n-nodes-youtube-transcription-kasha.youtubeTranscripter" + parts = node_type.lower().split('.') + for part in parts: + if 'youtube' in part: + service_name = 'YouTube' + break + elif 'telegram' in part: + service_name = 'Telegram' + break + elif 'discord' in part: + service_name = 'Discord' + break + + # Also check node names for service hints + for service_key, service_value in service_mappings.items(): + if service_key in node_name and service_value: + service_name = service_value + break + + # Add to integrations if valid service found + if service_name and service_name not in ['None', None]: + integrations.add(service_name) # Determine if complex based on node variety and count if len(nodes) > 10 and len(integrations) > 3: @@ -445,6 +640,76 @@ class WorkflowDatabase: 'last_indexed': datetime.datetime.now().isoformat() } + def get_service_categories(self) -> Dict[str, List[str]]: + """Get service categories for enhanced filtering.""" + return { + 'messaging': ['Telegram', 'Discord', 'Slack', 'WhatsApp', 'Mattermost', 'Microsoft Teams', 'Rocket.Chat'], + 'email': ['Gmail', 'Mailjet', 'Email (IMAP)', 'Email (SMTP)', 'Outlook'], + 'cloud_storage': ['Google Drive', 'Google Docs', 'Google Sheets', 'Dropbox', 'OneDrive', 'Box'], + 'database': ['PostgreSQL', 'MySQL', 'MongoDB', 'Redis', 'Airtable', 'Notion'], + 'project_management': ['Jira', 'GitHub', 'GitLab', 'Trello', 'Asana', 'Monday.com'], + 'ai_ml': ['OpenAI', 'Anthropic', 'Hugging Face'], + 'social_media': ['LinkedIn', 'Twitter/X', 'Facebook', 'Instagram'], + 'ecommerce': ['Shopify', 'Stripe', 'PayPal'], + 'analytics': ['Google Analytics', 'Mixpanel'], + 'calendar_tasks': ['Google Calendar', 'Google Tasks', 'Cal.com', 'Calendly'], + 'forms': ['Typeform', 'Google Forms', 'Form Trigger'], + 'development': ['Webhook', 'HTTP Request', 'GraphQL', 'Server-Sent Events', 'YouTube'] + } + + def search_by_category(self, category: str, limit: int = 50, offset: int = 0) -> Tuple[List[Dict], int]: + """Search workflows by service category.""" + categories = self.get_service_categories() + if category not in categories: + return [], 0 + + services = categories[category] + conn = sqlite3.connect(self.db_path) + conn.row_factory = sqlite3.Row + + # Build OR conditions for all services in category + service_conditions = [] + params = [] + for service in services: + service_conditions.append("integrations LIKE ?") + params.append(f'%"{service}"%') + + where_clause = " OR ".join(service_conditions) + + # Count total results + count_query = f"SELECT COUNT(*) as total FROM workflows WHERE {where_clause}" + cursor = conn.execute(count_query, params) + total = cursor.fetchone()['total'] + + # Get paginated results + query = f""" + SELECT * FROM workflows + WHERE {where_clause} + ORDER BY analyzed_at DESC + LIMIT {limit} OFFSET {offset} + """ + + cursor = conn.execute(query, params) + rows = cursor.fetchall() + + # Convert to dictionaries and parse JSON fields + results = [] + for row in rows: + workflow = dict(row) + workflow['integrations'] = json.loads(workflow['integrations'] or '[]') + raw_tags = json.loads(workflow['tags'] or '[]') + clean_tags = [] + for tag in raw_tags: + if isinstance(tag, dict): + clean_tags.append(tag.get('name', str(tag.get('id', 'tag')))) + else: + clean_tags.append(str(tag)) + workflow['tags'] = clean_tags + results.append(workflow) + + conn.close() + return results, total + def main(): """Command-line interface for workflow database.""" diff --git a/workflows.db b/workflows.db deleted file mode 100644 index 46a8528..0000000 Binary files a/workflows.db and /dev/null differ