Add Groq as AI provider + new inline AI tasks, extend database to support more prompt types

This commit is contained in:
2026-01-20 10:04:01 -05:00
parent 7218e7cc3f
commit 167c13c572
24 changed files with 3521 additions and 315 deletions

View File

@@ -0,0 +1,186 @@
/**
* AI Task Registry
*
* Simple registry pattern for AI tasks. Each task has:
* - id: Unique identifier
* - run: Async function that executes the task
*
* This allows adding new AI capabilities without modifying core code.
*/
const { createNameValidationTask, TASK_ID: NAME_TASK_ID } = require('./nameValidationTask');
const { createDescriptionValidationTask, TASK_ID: DESC_TASK_ID } = require('./descriptionValidationTask');
const { createSanityCheckTask, TASK_ID: SANITY_TASK_ID } = require('./sanityCheckTask');
/**
* Task IDs - frozen constants for type safety
*/
const TASK_IDS = Object.freeze({
// Inline validation (triggered on field blur)
VALIDATE_NAME: NAME_TASK_ID,
VALIDATE_DESCRIPTION: DESC_TASK_ID,
// Batch operations (triggered on user action)
SANITY_CHECK: SANITY_TASK_ID
});
/**
* Task Registry
*/
class TaskRegistry {
constructor() {
this.tasks = new Map();
}
/**
* Register a task
* @param {Object} task
* @param {string} task.id - Unique task identifier
* @param {Function} task.run - Async function: (payload) => result
* @param {string} [task.description] - Human-readable description
*/
register(task) {
if (!task?.id) {
throw new Error('Task must have an id');
}
if (typeof task.run !== 'function') {
throw new Error(`Task ${task.id} must have a run function`);
}
if (this.tasks.has(task.id)) {
throw new Error(`Task ${task.id} is already registered`);
}
this.tasks.set(task.id, task);
return this;
}
/**
* Get a task by ID
* @param {string} taskId
* @returns {Object|null}
*/
get(taskId) {
return this.tasks.get(taskId) || null;
}
/**
* Check if a task exists
* @param {string} taskId
* @returns {boolean}
*/
has(taskId) {
return this.tasks.has(taskId);
}
/**
* Run a task by ID
* @param {string} taskId
* @param {Object} payload - Task-specific input
* @returns {Promise<Object>} Task result
*/
async runTask(taskId, payload = {}) {
const task = this.get(taskId);
if (!task) {
throw new Error(`Unknown task: ${taskId}`);
}
try {
const result = await task.run(payload);
return {
success: true,
taskId,
...result
};
} catch (error) {
return {
success: false,
taskId,
error: error.message,
code: error.code
};
}
}
/**
* List all registered task IDs
* @returns {string[]}
*/
list() {
return Array.from(this.tasks.keys());
}
/**
* Get count of registered tasks
* @returns {number}
*/
size() {
return this.tasks.size;
}
}
// Singleton instance
let registry = null;
/**
* Get or create the task registry
* @returns {TaskRegistry}
*/
function getRegistry() {
if (!registry) {
registry = new TaskRegistry();
}
return registry;
}
/**
* Reset the registry (mainly for testing)
*/
function resetRegistry() {
registry = null;
}
/**
* Register all validation tasks with the registry
* Call this during initialization after the registry is created
*
* @param {Object} [logger] - Optional logger
*/
function registerAllTasks(logger = console) {
const reg = getRegistry();
// Register name validation
if (!reg.has(TASK_IDS.VALIDATE_NAME)) {
reg.register(createNameValidationTask());
logger.info(`[Tasks] Registered: ${TASK_IDS.VALIDATE_NAME}`);
}
// Register description validation
if (!reg.has(TASK_IDS.VALIDATE_DESCRIPTION)) {
reg.register(createDescriptionValidationTask());
logger.info(`[Tasks] Registered: ${TASK_IDS.VALIDATE_DESCRIPTION}`);
}
// Register sanity check
if (!reg.has(TASK_IDS.SANITY_CHECK)) {
reg.register(createSanityCheckTask());
logger.info(`[Tasks] Registered: ${TASK_IDS.SANITY_CHECK}`);
}
return reg;
}
module.exports = {
// Constants
TASK_IDS,
// Registry
TaskRegistry,
getRegistry,
resetRegistry,
registerAllTasks,
// Task factories (for custom registration)
createNameValidationTask,
createDescriptionValidationTask,
createSanityCheckTask
};