- Move 12+ outdated documentation files to docs-archive/ - Keep main directory clean with only essential files - Add archive README explaining the move - Main README.md is now the single source of truth for installation - Focus on Docker deployment as primary method 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
20 KiB
20 KiB
Ideas Generator 2025 - Implementation Guide
Complete Step-by-Step Development Plan
📋 Project Overview
Goal: Migrate Ideas Generator from Make.com workflow (OpenAI Assistants API) to local Node.js backend (OpenAI Responses API)
Current System: 48 specialized AI assistants via Make.com webhook → OpenAI Assistants API Target System: Local backend with PostgreSQL + OpenAI Responses API + Admin interface
Timeline: 5 weeks (25 working days) Architecture: Node.js + Express + PostgreSQL + Redis + OpenAI Responses API
🏗 Phase 1: Foundation Setup (Week 1 - Days 1-5)
Day 1: Project Structure & Environment
1.1 Create Project Structure
ideas-gen-2025/
├── server/ # Backend application
│ ├── package.json
│ ├── .env.example
│ ├── .env
│ ├── index.js # Main server file
│ ├── config/
│ │ ├── database.js # PostgreSQL configuration
│ │ ├── redis.js # Redis configuration
│ │ └── openai.js # OpenAI client setup
│ ├── models/
│ │ ├── User.js
│ │ ├── Assistant.js
│ │ ├── Conversation.js
│ │ ├── Message.js
│ │ └── index.js # Model exports
│ ├── routes/
│ │ ├── auth.js # Authentication (dev bypass)
│ │ ├── assistants.js # Assistant CRUD
│ │ ├── conversations.js # Conversation management
│ │ ├── messages.js # Message handling
│ │ ├── chat.js # Main chat endpoint
│ │ └── admin.js # Admin interface APIs
│ ├── middleware/
│ │ ├── auth.js # Authentication middleware
│ │ ├── validation.js # Request validation
│ │ ├── errorHandler.js # Error handling
│ │ └── logging.js # Request logging
│ ├── utils/
│ │ ├── assistantManager.js # Dynamic assistant loading
│ │ ├── systemPrompts.js # TOV integration
│ │ ├── titleGenerator.js # Auto-title generation
│ │ └── contentFilter.js # Security filtering
│ └── migrations/
│ └── 001_initial_schema.sql
├── admin/ # Admin interface (React)
│ ├── package.json
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ └── services/
│ └── public/
├── frontend/ # Existing frontend (minimal changes)
│ ├── index.html
│ ├── js/
│ └── css/
└── docs/ # Implementation documentation
├── API.md
├── DATABASE.md
└── DEPLOYMENT.md
1.2 Initialize Backend
cd server
npm init -y
npm install express cors dotenv
npm install @sequelize/core pg redis openai
npm install joi express-rate-limit helmet morgan
npm install --save-dev nodemon concurrently
1.3 Create package.json Scripts
{
"scripts": {
"dev": "nodemon index.js",
"start": "node index.js",
"db:migrate": "node migrations/migrate.js",
"db:seed": "node migrations/seed.js",
"test": "jest"
}
}
1.4 Environment Configuration
Create .env.example and .env files with:
# Database
DATABASE_URL=postgres://localhost:5432/ideas_gen_dev
DATABASE_HOST=localhost
DATABASE_NAME=ideas_gen_dev
DATABASE_USER=postgres
DATABASE_PASS=password
# Redis
REDIS_URL=redis://localhost:6379
# OpenAI
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_ORG_ID=your_org_id_here
# Server
PORT=3000
NODE_ENV=development
# Development flags
SKIP_AUTH=true
ENABLE_CORS=true
LOG_LEVEL=debug
Day 2: Database Setup
2.1 Install & Configure PostgreSQL
# macOS
brew install postgresql
brew services start postgresql
createdb ideas_gen_dev
# Ubuntu
sudo apt install postgresql postgresql-contrib
sudo systemctl start postgresql
sudo -u postgres createdb ideas_gen_dev
2.2 Database Schema (migrations/001_initial_schema.sql)
-- Users table
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
role VARCHAR(50) DEFAULT 'user',
permissions JSONB DEFAULT '[]'::jsonb,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
last_login TIMESTAMPTZ,
metadata JSONB DEFAULT '{}'::jsonb
);
-- Assistants table (dynamic configuration)
CREATE TABLE assistants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
key VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
system_prompt TEXT NOT NULL,
configuration JSONB NOT NULL DEFAULT '{
"model": "gpt-4o",
"temperature": 0.7,
"max_tokens": 1000,
"tools": []
}'::jsonb,
metadata JSONB DEFAULT '{
"category": "",
"technique_focus": "",
"tags": [],
"client_specific": null
}'::jsonb,
initial_message TEXT NOT NULL,
-- Admin fields
status VARCHAR(20) DEFAULT 'active',
version INTEGER DEFAULT 1,
created_by UUID REFERENCES users(id),
updated_by UUID REFERENCES users(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted BOOLEAN DEFAULT false
);
-- Conversations table
CREATE TABLE conversations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id),
assistant_key VARCHAR(100) NOT NULL,
title VARCHAR(255),
last_response_id VARCHAR(255),
-- Configuration snapshots
assistant_config JSONB NOT NULL,
tov_config JSONB DEFAULT '{}'::jsonb,
-- Analytics
message_count INTEGER DEFAULT 0,
total_tokens INTEGER DEFAULT 0,
total_cost DECIMAL(10,6) DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
deleted BOOLEAN DEFAULT false
);
-- Messages table
CREATE TABLE messages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
conversation_id UUID NOT NULL REFERENCES conversations(id),
role VARCHAR(20) NOT NULL CHECK (role IN ('user', 'assistant')),
content TEXT NOT NULL,
content_plain TEXT NOT NULL,
-- OpenAI metadata
response_metadata JSONB DEFAULT '{}'::jsonb,
token_usage JSONB DEFAULT '{
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
}'::jsonb,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_role ON users(role);
CREATE INDEX idx_assistants_key ON assistants(key);
CREATE INDEX idx_assistants_status ON assistants(status);
CREATE INDEX idx_conversations_user ON conversations(user_id);
CREATE INDEX idx_conversations_assistant ON conversations(assistant_key);
CREATE INDEX idx_messages_conversation ON messages(conversation_id);
CREATE INDEX idx_messages_role ON messages(role);
2.3 Sequelize Models Setup
Create models/index.js with database connection and model definitions.
Day 3: OpenAI Responses API Integration
3.1 OpenAI Client Configuration (config/openai.js)
const OpenAI = require('openai');
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
organization: process.env.OPENAI_ORG_ID,
});
// Content moderation wrapper
async function moderateContent(content) {
try {
const moderation = await openai.moderations.create({
input: content
});
return moderation.results[0];
} catch (error) {
console.error('Content moderation failed:', error);
return { flagged: false }; // Fail open for development
}
}
// Title generation using Chat Completions
async function generateTitle(userMessage) {
try {
const completion = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: 'You are a conversation title generator. Create a concise title (2-4 words) that captures the essence of the conversation. No quotes, no prefixes like "Title:".'
},
{
role: 'user',
content: `Generate a short title for a conversation that starts with: "${userMessage}"`
}
],
temperature: 0.3,
max_tokens: 10
});
return completion.choices[0].message.content.trim();
} catch (error) {
console.error('Title generation failed:', error);
return 'New Conversation';
}
}
module.exports = { openai, moderateContent, generateTitle };
3.2 Assistant Manager (utils/assistantManager.js)
const { Assistant } = require('../models');
const NodeCache = require('node-cache');
class AssistantManager {
constructor() {
// Cache for 5 minutes
this.cache = new NodeCache({ stdTTL: 300 });
}
async getAssistant(key) {
// Check cache first
const cached = this.cache.get(key);
if (cached) return cached;
// Load from database
const assistant = await Assistant.findOne({
where: { key, status: 'active', deleted: false }
});
if (!assistant) {
throw new Error(`Assistant '${key}' not found or inactive`);
}
const config = {
key: assistant.key,
name: assistant.name,
system_prompt: assistant.system_prompt,
model: assistant.configuration.model,
temperature: assistant.configuration.temperature,
max_tokens: assistant.configuration.max_tokens,
initial_message: assistant.initial_message,
category: assistant.metadata.category
};
// Cache the result
this.cache.set(key, config);
return config;
}
invalidateCache(key) {
this.cache.del(key);
}
async getAllActiveAssistants() {
return await Assistant.findAll({
where: { status: 'active', deleted: false },
attributes: ['key', 'name', 'metadata', 'initial_message'],
order: [['name', 'ASC']]
});
}
}
module.exports = new AssistantManager();
Day 4: Core API Endpoints
4.1 Main Chat Endpoint (routes/chat.js)
const express = require('express');
const router = express.Router();
const { v4: uuidv4 } = require('uuid');
const { openai, moderateContent, generateTitle } = require('../config/openai');
const assistantManager = require('../utils/assistantManager');
const { buildSystemPrompt } = require('../utils/systemPrompts');
const { Conversation, Message } = require('../models');
router.post('/', async (req, res) => {
try {
const { user_id = 'dev@local.dev' } = req.auth || {}; // Dev user
const { ConversationID, AssistantKey, TOV_Key, Message: userMessage } = req.body;
// Validate required fields
if (!AssistantKey || !TOV_Key || !userMessage) {
return res.status(400).json({ error: 'Missing required fields' });
}
// Content moderation
const moderation = await moderateContent(userMessage);
if (moderation.flagged) {
return res.status(400).json({ error: 'Content flagged by moderation' });
}
// Get assistant configuration
const assistantConfig = await assistantManager.getAssistant(AssistantKey);
let conversation;
let isNewConversation = !ConversationID;
let previousResponseId = null;
if (isNewConversation) {
// Create new conversation
conversation = await Conversation.create({
id: uuidv4(),
user_id,
assistant_key: AssistantKey,
assistant_config: assistantConfig,
tov_config: { key: TOV_Key }
});
} else {
// Get existing conversation
conversation = await Conversation.findOne({
where: { id: ConversationID, user_id }
});
if (!conversation) {
return res.status(404).json({ error: 'Conversation not found' });
}
previousResponseId = conversation.last_response_id;
}
// Build system prompt with TOV
const systemPrompt = buildSystemPrompt(assistantConfig, TOV_Key);
// Call OpenAI Responses API
const response = await openai.responses.create({
model: assistantConfig.model,
input: userMessage,
system: systemPrompt,
temperature: assistantConfig.temperature,
max_tokens: assistantConfig.max_tokens,
store: true, // Enable conversation memory
previous_response_id: previousResponseId
});
// Store user message
await Message.create({
conversation_id: conversation.id,
role: 'user',
content: userMessage,
content_plain: userMessage
});
// Extract assistant response
const assistantMessage = response.choices[0].message.content;
// Store assistant message
await Message.create({
conversation_id: conversation.id,
role: 'assistant',
content: assistantMessage,
content_plain: assistantMessage,
response_metadata: { response_id: response.id },
token_usage: response.usage || {}
});
// Update conversation
await conversation.update({
last_response_id: response.id,
message_count: conversation.message_count + 2, // user + assistant
total_tokens: (conversation.total_tokens || 0) + (response.usage?.total_tokens || 0),
updated_at: new Date()
});
// Generate title for new conversations
if (isNewConversation) {
const title = await generateTitle(userMessage);
await conversation.update({ title });
return res.json({
conversation_id: conversation.id,
conversation_title: title,
message: assistantMessage
});
}
res.json({
conversation_id: conversation.id,
message: assistantMessage
});
} catch (error) {
console.error('Chat error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;
Day 5: Frontend Integration
5.1 Update Frontend API calls (js/script.js modifications)
// Update variables for local development
const isDevelopment = true;
const make_url = isDevelopment ?
"http://localhost:3000/api" :
"https://hook.us1.make.celonis.com/htn0fepeoai19d1unx6fqm5qd5ptk5px";
// Update gcp_fetch for local backend
async function gcp_fetch(url, options = {}) {
console.log("Fetching:", url);
const defaultOptions = {
method: "GET",
headers: {
"Content-type": "application/json",
}
};
return await fetch(url, { ...defaultOptions, ...options });
}
// Update sendMessage function for Responses API
const sendMessage = async () => {
if (!assistant_key) {
alert("Please Select an Assistant");
return false;
}
if (!tov_key) {
alert("Please Select a Tone of Voice");
return false;
}
if (sending_message) return false;
sending_message = true;
const message = maskUKBankDetails(document.getElementById("message-input")?.value);
document.getElementById("message-input").value = "";
// Add user message to UI
Array.from(document.getElementsByClassName("chat-message-appear"))?.forEach(el =>
el.classList.remove("chat-message-appear")
);
document.getElementById("chat").innerHTML +=
chat_message_user_new.replaceAll("{CONTENT}", md_converter?.makeHtml(message));
// Scroll to bottom
document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;
// Show loading
setTimeout(() => {
document.getElementById("chat").innerHTML += chat_message_assistant_loading_dots;
document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;
}, 1000);
try {
const response = await fetch(make_url + '/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
ConversationID: conversation_id || undefined,
AssistantKey: assistant_key,
TOV_Key: tov_key,
Message: message
})
});
const data = await response.json();
if (response.ok) {
// Update conversation ID
if (data.conversation_id) {
conversation_id = data.conversation_id;
}
// Update conversation title for new conversations
if (data.conversation_title) {
// Add to conversations list or update title
if (conversations.findIndex(c => c.id === data.conversation_id) === -1) {
conversations = [{
id: data.conversation_id,
title: data.conversation_title,
assistant_key: assistant_key,
tov_key: tov_key
}].concat(conversations);
// Update conversations list in UI
updateConversationsList();
}
}
// Remove loading and add response
Array.from(document.getElementsByClassName("chat-message-loading-dots"))?.forEach(el => el.remove());
document.getElementById("chat").innerHTML +=
chat_message_assistant_new.replaceAll("{CONTENT}", data.message);
} else {
console.error('API Error:', data.error);
alert(data.error || 'An error occurred');
}
} catch (error) {
console.error('Network error:', error);
alert('Network error occurred');
} finally {
sending_message = false;
document.getElementById("chat").scrollTop = document.getElementById("chat").scrollHeight;
}
};
🎯 Implementation Checkpoints
Week 1 Success Criteria:
- Project structure created and dependencies installed
- PostgreSQL database running with schema
- OpenAI Responses API integration working
- Basic chat endpoint functional
- Frontend making API calls to local backend
- Can create new conversations and continue existing ones
- Assistant configurations loading dynamically
Next Steps for Week 2:
- Import all 48 assistant configurations from CSV
- Create admin interface for assistant management
- Implement remaining API endpoints (conversations, messages, assistants)
- Add comprehensive error handling and logging
📝 Quick Reference Commands
Development Startup:
# Start PostgreSQL
brew services start postgresql # macOS
sudo systemctl start postgresql # Ubuntu
# Start Redis (optional for Week 1)
redis-server
# Start backend
cd server
npm run dev
# Start frontend (separate terminal)
cd frontend
python -m http.server 8080 # or use Live Server
Database Commands:
# Connect to database
psql ideas_gen_dev
# Run migration
npm run db:migrate
# Check tables
\dt
Git Commands:
# Commit progress
git add .
git commit -m "Phase 1 Day X: [Description]"
git push origin ideas-gen-2025
This implementation guide provides everything needed to start Week 1 development. Each day builds incrementally toward a functional local backend system.