import { Sequelize, DataTypes } from 'sequelize'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __dirname = dirname(fileURLToPath(import.meta.url)); export const sequelize = new Sequelize({ dialect: 'sqlite', storage: join(__dirname, '../data/jobs.sqlite'), logging: false, }); export const Job = sequelize.define('Job', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true, }, prompt: { type: DataTypes.TEXT, allowNull: false, }, // Base64 data URL stored for replay; in production use object storage imageDataUrl: { type: DataTypes.TEXT, allowNull: false, }, imageMimeType: { type: DataTypes.STRING(64), allowNull: false, defaultValue: 'image/jpeg', }, status: { // queued | running | done | error type: DataTypes.STRING(16), allowNull: false, defaultValue: 'queued', }, result: { type: DataTypes.TEXT, allowNull: true, }, errorMessage: { type: DataTypes.TEXT, allowNull: true, }, model: { type: DataTypes.STRING(128), allowNull: false, defaultValue: 'qwen3.5:397b-cloud', }, inputTokens: { type: DataTypes.INTEGER, allowNull: true }, outputTokens: { type: DataTypes.INTEGER, allowNull: true }, // ---- Bounding-box detection fields (Pass 1) ---- detectedSubject: { type: DataTypes.STRING(256), allowNull: true, comment: 'Short description of what the LLM identified as the subject', }, bboxX: { type: DataTypes.FLOAT, allowNull: true }, bboxY: { type: DataTypes.FLOAT, allowNull: true }, bboxWidth: { type: DataTypes.FLOAT, allowNull: true }, bboxHeight: { type: DataTypes.FLOAT, allowNull: true }, // Cropped image data URL (stored for UI preview / debugging) croppedImageDataUrl: { type: DataTypes.TEXT, allowNull: true, }, }, { tableName: 'jobs', timestamps: true, }); export async function initDb() { const { mkdirSync } = await import('fs'); mkdirSync(join(__dirname, '../data'), { recursive: true }); // alter: true adds new columns to an existing table without dropping data await sequelize.sync({ alter: true }); }