update context API
All checks were successful
Deploy to Production / deploy (push) Successful in 19s
All checks were successful
Deploy to Production / deploy (push) Successful in 19s
This commit is contained in:
50
add-knowledge-column.js
Normal file
50
add-knowledge-column.js
Normal file
@@ -0,0 +1,50 @@
|
||||
const {sequelize} = require('./config/database');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ Database connected');
|
||||
|
||||
// Check current columns
|
||||
const [cols] = await sequelize.query('DESCRIBE context');
|
||||
console.log('\n📋 Current columns:');
|
||||
cols.forEach(c => console.log(` - ${c.Field}`));
|
||||
|
||||
const columnNames = cols.map(c => c.Field);
|
||||
|
||||
// Add knowledge column if not exists
|
||||
if (!columnNames.includes('knowledge')) {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
ADD COLUMN knowledge TEXT NULL
|
||||
COMMENT 'Additional knowledge or information'
|
||||
`);
|
||||
console.log('\n✅ Added knowledge column');
|
||||
} else {
|
||||
console.log('\n✅ knowledge column already exists');
|
||||
}
|
||||
|
||||
// Add status column if not exists
|
||||
if (!columnNames.includes('status')) {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
ADD COLUMN status INT DEFAULT 0
|
||||
COMMENT '0: Draft, 1: Enriched, 2: Prompt_Ready, 3: Generating, 4: Image_Ready, 5: Approved'
|
||||
`);
|
||||
console.log('✅ Added status column');
|
||||
} else {
|
||||
console.log('✅ status column already exists');
|
||||
}
|
||||
|
||||
// Show final structure
|
||||
const [finalCols] = await sequelize.query('DESCRIBE context');
|
||||
console.log('\n📊 Final Context table structure:');
|
||||
finalCols.forEach((c, i) => console.log(` ${i+1}. ${c.Field} (${c.Type})`));
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
170
alter-context-table.js
Normal file
170
alter-context-table.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Alter context table to update grade column and add/update other fields
|
||||
*/
|
||||
const { sequelize } = require('./config/database');
|
||||
|
||||
async function alterContextTable() {
|
||||
try {
|
||||
console.log('🔄 Starting context table alteration...');
|
||||
|
||||
// Test connection first
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ Database connection OK');
|
||||
|
||||
// Drop old columns if they exist
|
||||
try {
|
||||
await sequelize.query(`ALTER TABLE context DROP COLUMN IF EXISTS difficulty`);
|
||||
console.log('✅ Dropped old difficulty column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ difficulty column might not exist');
|
||||
}
|
||||
|
||||
try {
|
||||
await sequelize.query(`ALTER TABLE context DROP COLUMN IF EXISTS isPrompt`);
|
||||
console.log('✅ Dropped old isPrompt column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ isPrompt column might not exist');
|
||||
}
|
||||
|
||||
try {
|
||||
await sequelize.query(`ALTER TABLE context DROP COLUMN IF EXISTS isList`);
|
||||
console.log('✅ Dropped old isList column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ isList column might not exist');
|
||||
}
|
||||
|
||||
try {
|
||||
await sequelize.query(`ALTER TABLE context DROP COLUMN IF EXISTS isApprove`);
|
||||
console.log('✅ Dropped old isApprove column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ isApprove column might not exist');
|
||||
}
|
||||
|
||||
try {
|
||||
await sequelize.query(`ALTER TABLE context DROP COLUMN IF EXISTS prompt`);
|
||||
console.log('✅ Dropped old prompt column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ prompt column might not exist');
|
||||
}
|
||||
|
||||
// Modify grade column (change from VARCHAR to INT)
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
MODIFY COLUMN grade INT NOT NULL DEFAULT 100
|
||||
COMMENT 'It is number of gradeX100 + unitX10 + lesson (e.g., Grade 1 Unit 2 Lesson 3 = 123)'
|
||||
`);
|
||||
console.log('✅ Modified grade column to INT');
|
||||
} catch (error) {
|
||||
console.log('⚠️ Grade column modification error:', error.message);
|
||||
// Try adding if not exists
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
ADD COLUMN grade INT NOT NULL DEFAULT 100
|
||||
COMMENT 'It is number of gradeX100 + unitX10 + lesson (e.g., Grade 1 Unit 2 Lesson 3 = 123)'
|
||||
`);
|
||||
console.log('✅ Added grade column');
|
||||
} catch (addError) {
|
||||
console.log('⚠️ Could not add grade column:', addError.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Modify knowledge column
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
MODIFY COLUMN knowledge TEXT DEFAULT ''
|
||||
COMMENT 'Additional knowledge or information'
|
||||
`);
|
||||
console.log('✅ Modified knowledge column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ Knowledge column might already be correct');
|
||||
}
|
||||
|
||||
// Rename prompt to promptForImage if needed
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
CHANGE COLUMN prompt promptForImage JSON
|
||||
COMMENT 'Prompt configuration object'
|
||||
`);
|
||||
console.log('✅ Renamed prompt to promptForImage');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ Column might already be named promptForImage');
|
||||
}
|
||||
|
||||
// Modify promptForImage if it exists
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
MODIFY COLUMN promptForImage JSON
|
||||
COMMENT 'Prompt configuration object'
|
||||
`);
|
||||
console.log('✅ Modified promptForImage column');
|
||||
} catch (error) {
|
||||
// Try adding if not exists
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
ADD COLUMN promptForImage JSON
|
||||
COMMENT 'Prompt configuration object'
|
||||
`);
|
||||
console.log('✅ Added promptForImage column');
|
||||
} catch (addError) {
|
||||
console.log('ℹ️ promptForImage column might already exist');
|
||||
}
|
||||
}
|
||||
|
||||
// Modify max column
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
MODIFY COLUMN max INT DEFAULT 1
|
||||
COMMENT 'Maximum number of images or items'
|
||||
`);
|
||||
console.log('✅ Modified max column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ max column might already be correct');
|
||||
}
|
||||
|
||||
// Add status column if not exists
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
ADD COLUMN status INT DEFAULT 0
|
||||
COMMENT '0: Draft, 1: Enriched, 2: Prompt_Ready, 3: Generating, 4: Image_Ready, 5: Approved'
|
||||
`);
|
||||
console.log('✅ Added status column');
|
||||
} catch (error) {
|
||||
console.log('ℹ️ status column might already exist');
|
||||
// Try modifying if exists
|
||||
try {
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
MODIFY COLUMN status INT DEFAULT 0
|
||||
COMMENT '0: Draft, 1: Enriched, 2: Prompt_Ready, 3: Generating, 4: Image_Ready, 5: Approved'
|
||||
`);
|
||||
console.log('✅ Modified status column');
|
||||
} catch (modError) {
|
||||
console.log('ℹ️ Status column might already be correct');
|
||||
}
|
||||
}
|
||||
|
||||
// Show final structure
|
||||
const [columns] = await sequelize.query('DESCRIBE context');
|
||||
console.log('\n📊 Context table structure:');
|
||||
columns.forEach((col, index) => {
|
||||
console.log(` ${index + 1}. ${col.Field} (${col.Type}) ${col.Null === 'NO' ? 'NOT NULL' : 'NULL'} ${col.Default ? `DEFAULT ${col.Default}` : ''}`);
|
||||
});
|
||||
|
||||
console.log('\n✅ Context table alteration complete!');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Error altering context table:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
alterContextTable();
|
||||
16
check-columns.js
Normal file
16
check-columns.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const {sequelize} = require('./config/database');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
const [cols] = await sequelize.query('DESCRIBE context');
|
||||
console.log('\n📊 Context table columns:');
|
||||
cols.forEach((c, i) => {
|
||||
console.log(` ${i+1}. ${c.Field} (${c.Type})`);
|
||||
});
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
18
cleanup-context.js
Normal file
18
cleanup-context.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const {sequelize} = require('./config/database');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
await sequelize.query('ALTER TABLE context DROP COLUMN is_prompt, DROP COLUMN is_list, DROP COLUMN is_approve');
|
||||
console.log('✅ Dropped old columns');
|
||||
|
||||
const [cols] = await sequelize.query('DESCRIBE context');
|
||||
console.log('\n📊 Final Context table:');
|
||||
cols.forEach(c => console.log(` ${c.Field} (${c.Type})`));
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
@@ -1,20 +1,67 @@
|
||||
const { Context } = require('../models');
|
||||
|
||||
/**
|
||||
* Context Controller
|
||||
* Context Controller - Workflow-based status management
|
||||
* Status flow: 0 (Draft) -> 1 (Enriched) -> 2 (Prompt Ready) -> 3 (Generating) -> 4 (Image Ready) -> 5 (Approved)
|
||||
*/
|
||||
class ContextController {
|
||||
/**
|
||||
* Get all contexts with pagination and filters
|
||||
* Create new context - Status 0 (Draft)
|
||||
* Required fields: title, desc, grade
|
||||
*/
|
||||
async getAllContexts(req, res, next) {
|
||||
async createContext(req, res, next) {
|
||||
try {
|
||||
const { page = 1, limit = 50, type, title } = req.query;
|
||||
const { title, desc, grade, type } = req.body;
|
||||
|
||||
// Validate required fields
|
||||
if (!title || !desc || !grade) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Title, desc, and grade are required'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate grade format (gradeX100 + unitX10 + lesson)
|
||||
const gradeNum = parseInt(grade);
|
||||
if (isNaN(gradeNum) || gradeNum < 100) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Grade must be in format: gradeX100 + unitX10 + lesson (e.g., 123 for Grade 1 Unit 2 Lesson 3)'
|
||||
});
|
||||
}
|
||||
|
||||
const context = await Context.create({
|
||||
title,
|
||||
desc,
|
||||
grade: gradeNum,
|
||||
type: type || 'general',
|
||||
status: 0, // Draft
|
||||
context: '',
|
||||
knowledge: ''
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: 'Context created successfully',
|
||||
data: context
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contexts by status
|
||||
*/
|
||||
async getContextsByStatus(req, res, next) {
|
||||
try {
|
||||
const { status } = req.params;
|
||||
const { page = 1, limit = 50, type, grade } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const where = {};
|
||||
const where = { status: parseInt(status) };
|
||||
if (type) where.type = type;
|
||||
if (title) where.title = title;
|
||||
if (grade) where.grade = parseInt(grade);
|
||||
|
||||
const { count, rows } = await Context.findAndCountAll({
|
||||
where,
|
||||
@@ -41,7 +88,219 @@ class ContextController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get context by UUID
|
||||
* Update knowledge - Status 0 -> 1 (Enriched)
|
||||
*/
|
||||
async enrichContext(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { knowledge } = req.body;
|
||||
|
||||
if (!knowledge) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Knowledge is required'
|
||||
});
|
||||
}
|
||||
|
||||
const context = await Context.findByPk(id);
|
||||
if (!context) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Context not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (context.status !== 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Context must be in Draft status (0) to enrich'
|
||||
});
|
||||
}
|
||||
|
||||
await context.update({
|
||||
knowledge,
|
||||
status: 1
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Context enriched successfully',
|
||||
data: context
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update context and img_prompt - Status 1 -> 2 (Prompt Ready)
|
||||
*/
|
||||
async preparePrompt(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { context, img_prompt } = req.body;
|
||||
|
||||
if (!context || !img_prompt) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Context and img_prompt are required'
|
||||
});
|
||||
}
|
||||
|
||||
const contextRecord = await Context.findByPk(id);
|
||||
if (!contextRecord) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Context not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (contextRecord.status !== 1) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Context must be in Enriched status (1) to prepare prompt'
|
||||
});
|
||||
}
|
||||
|
||||
await contextRecord.update({
|
||||
context,
|
||||
img_prompt,
|
||||
status: 2
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Prompt prepared successfully',
|
||||
data: contextRecord
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update status to 3 (Generating) or back to 1 (Enriched)
|
||||
*/
|
||||
async updateStatusFromPromptReady(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { status } = req.body;
|
||||
|
||||
if (![1, 3].includes(parseInt(status))) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Status must be 1 (Enriched) or 3 (Generating)'
|
||||
});
|
||||
}
|
||||
|
||||
const context = await Context.findByPk(id);
|
||||
if (!context) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Context not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (context.status !== 2) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Context must be in Prompt Ready status (2) to update'
|
||||
});
|
||||
}
|
||||
|
||||
await context.update({ status: parseInt(status) });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Status updated to ${status}`,
|
||||
data: context
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add images - Status 3 -> 4 (Image Ready)
|
||||
*/
|
||||
async addImages(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { image } = req.body;
|
||||
|
||||
if (!image || !Array.isArray(image) || image.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Image must be a non-empty array of URLs'
|
||||
});
|
||||
}
|
||||
|
||||
const context = await Context.findByPk(id);
|
||||
if (!context) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Context not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (context.status !== 3) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Context must be in Generating status (3) to add images'
|
||||
});
|
||||
}
|
||||
|
||||
await context.update({
|
||||
image,
|
||||
status: 4
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Images added successfully',
|
||||
data: context
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve context - Status 4 -> 5 (Approved)
|
||||
*/
|
||||
async approveContext(req, res, next) {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const context = await Context.findByPk(id);
|
||||
if (!context) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Context not found'
|
||||
});
|
||||
}
|
||||
|
||||
if (context.status !== 4) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Context must be in Image Ready status (4) to approve'
|
||||
});
|
||||
}
|
||||
|
||||
await context.update({ status: 5 });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Context approved successfully',
|
||||
data: context
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get context by UUID (for viewing details)
|
||||
*/
|
||||
async getContextById(req, res, next) {
|
||||
try {
|
||||
@@ -65,16 +324,36 @@ class ContextController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new context
|
||||
* Get all contexts with pagination and filters
|
||||
*/
|
||||
async createContext(req, res, next) {
|
||||
async getAllContexts(req, res, next) {
|
||||
try {
|
||||
const context = await Context.create(req.body);
|
||||
const { page = 1, limit = 50, type, grade, status } = req.query;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
res.status(201).json({
|
||||
const where = {};
|
||||
if (type) where.type = type;
|
||||
if (grade) where.grade = parseInt(grade);
|
||||
if (status !== undefined) where.status = parseInt(status);
|
||||
|
||||
const { count, rows } = await Context.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
order: [['created_at', 'DESC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Context created successfully',
|
||||
data: context
|
||||
data: {
|
||||
contexts: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: Math.ceil(count / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
@@ -82,7 +361,7 @@ class ContextController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update context
|
||||
* Update context (general update - use with caution)
|
||||
*/
|
||||
async updateContext(req, res, next) {
|
||||
try {
|
||||
|
||||
@@ -15,11 +15,18 @@ const Context = sequelize.define('Context', {
|
||||
},
|
||||
context: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
allowNull: true,
|
||||
comment: 'Context description'
|
||||
},
|
||||
grade : {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 100,
|
||||
comment: 'It is number of gradeX100 + unitX10 + lesson (e.g., Grade 1 Unit 2 Lesson 3 = 123)'
|
||||
},
|
||||
knowledge: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'Additional knowledge or information'
|
||||
},
|
||||
type: {
|
||||
@@ -31,7 +38,7 @@ const Context = sequelize.define('Context', {
|
||||
type: DataTypes.TEXT,
|
||||
comment: 'Detailed description or requirement'
|
||||
},
|
||||
prompt: {
|
||||
img_prompt: {
|
||||
type: DataTypes.JSON,
|
||||
comment: 'Prompt configuration object'
|
||||
},
|
||||
@@ -39,30 +46,15 @@ const Context = sequelize.define('Context', {
|
||||
type: DataTypes.JSON,
|
||||
comment: 'Array of image URLs'
|
||||
},
|
||||
difficulty: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 1,
|
||||
comment: 'Difficulty level (1-10)'
|
||||
},
|
||||
max: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
defaultValue: 1,
|
||||
comment: 'Maximum number of images or items'
|
||||
},
|
||||
isPrompt: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: 'Prompt created (0/1)'
|
||||
},
|
||||
isList: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: 'Waiting for more images (0/1)'
|
||||
},
|
||||
isApprove: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
comment: 'Teacher approval status (0/1)'
|
||||
status: {
|
||||
type: DataTypes.INTEGER, // Hoặc DataTypes.ENUM('DRAFT', 'ENRICHED', 'PENDING_IMAGE', ...)
|
||||
defaultValue: 0,
|
||||
comment: '0: Draft, 1: Enriched, 2: Prompt_Ready, 3: Generating, 4: Image_Ready, 5: Approved'
|
||||
},
|
||||
created_at: {
|
||||
type: DataTypes.DATE,
|
||||
@@ -75,6 +67,7 @@ const Context = sequelize.define('Context', {
|
||||
}, {
|
||||
tableName: 'context',
|
||||
timestamps: true,
|
||||
underscored: false,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
indexes: [
|
||||
|
||||
@@ -24,7 +24,8 @@ const Vocab = sequelize.define('Vocab', {
|
||||
// Ví dụ 111 là grade 1, unit 1, lesson 1
|
||||
location: {
|
||||
type: DataTypes.INTEGER,
|
||||
comment: 'Location or source of the vocabulary'
|
||||
defaultValue: 100,
|
||||
comment: 'It is number of gradeX100 + unitX10 + lesson (e.g., Grade 1 Unit 2 Lesson 3 = 123)'
|
||||
},
|
||||
// Loại biến thể (V1, V2, V3, V_ing, Noun_Form...)
|
||||
form_key: {
|
||||
|
||||
708
public/contexts.html
Normal file
708
public/contexts.html
Normal file
@@ -0,0 +1,708 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Context Workflow Manager</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5em;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.workflow-info {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.workflow-info h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.status-flow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 8px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
padding: 5px 12px;
|
||||
border-radius: 15px;
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.status-0 { background: #ffd700; color: #000; }
|
||||
.status-1 { background: #87ceeb; color: #000; }
|
||||
.status-2 { background: #90ee90; color: #000; }
|
||||
.status-3 { background: #ffa500; color: #fff; }
|
||||
.status-4 { background: #9370db; color: #fff; }
|
||||
.status-5 { background: #32cd32; color: #fff; }
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 80px;
|
||||
resize: vertical;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 25px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #667eea;
|
||||
display: none;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.result.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.success {
|
||||
border-left-color: #28a745;
|
||||
background: #d4edda;
|
||||
}
|
||||
|
||||
.error {
|
||||
border-left-color: #dc3545;
|
||||
background: #f8d7da;
|
||||
}
|
||||
|
||||
.context-list {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.context-item {
|
||||
background: #f8f9fa;
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 5px;
|
||||
border-left: 4px solid #667eea;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.context-item:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.context-item strong {
|
||||
color: #667eea;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.context-item small {
|
||||
color: #666;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #28a745;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
margin: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.selected-uuid {
|
||||
background: #fff3cd;
|
||||
border: 2px solid #ffc107;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 15px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selected-uuid.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.quick-actions button {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.small-note {
|
||||
font-size: 0.85em;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🎯 Context Workflow Manager</h1>
|
||||
|
||||
<div class="workflow-info">
|
||||
<h3>📊 Workflow Status Flow</h3>
|
||||
<div class="status-flow">
|
||||
<div class="status-badge">0: Draft</div>
|
||||
<div class="status-badge">1: Enriched</div>
|
||||
<div class="status-badge">2: Prompt Ready</div>
|
||||
<div class="status-badge">3: Generating</div>
|
||||
<div class="status-badge">4: Image Ready</div>
|
||||
<div class="status-badge">5: Approved</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status 0: Create Draft -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>1️⃣ Create Context <span class="status-indicator status-0">Status: 0</span></h3>
|
||||
<div class="form-group">
|
||||
<label>Title *</label>
|
||||
<input type="text" id="createTitle" placeholder="e.g., Family Members">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Description *</label>
|
||||
<textarea id="createDesc" placeholder="e.g., Learn vocabulary about family members"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Grade * (Format: gradeX100 + unitX10 + lesson)</label>
|
||||
<input type="number" id="createGrade" placeholder="e.g., 123 = Grade 1, Unit 2, Lesson 3">
|
||||
<div class="small-note">Example: 123 = Grade 1, Unit 2, Lesson 3</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Type</label>
|
||||
<input type="text" id="createType" value="vocabulary" placeholder="vocabulary, grammar, etc.">
|
||||
</div>
|
||||
<button onclick="createContext()">Create Context</button>
|
||||
<div class="result" id="createResult"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 Get Contexts by Status <span class="status-indicator status-0">Status: 0</span></h3>
|
||||
<button onclick="getContextsByStatus(0)">Get Status 0 (Draft)</button>
|
||||
<div class="result" id="status0Result"></div>
|
||||
<div class="context-list" id="status0List"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status 1: Enrich -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>2️⃣ Enrich Context <span class="status-indicator status-1">0 → 1</span></h3>
|
||||
<div class="selected-uuid" id="enrichSelected"></div>
|
||||
<div class="form-group">
|
||||
<label>Context UUID *</label>
|
||||
<input type="text" id="enrichUuid" placeholder="Paste UUID or click from list above">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Knowledge *</label>
|
||||
<textarea id="enrichKnowledge" placeholder="Additional knowledge and information about this context"></textarea>
|
||||
</div>
|
||||
<button onclick="enrichContext()">Enrich (Status 0 → 1)</button>
|
||||
<div class="result" id="enrichResult"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 Get Contexts by Status <span class="status-indicator status-1">Status: 1</span></h3>
|
||||
<button onclick="getContextsByStatus(1)">Get Status 1 (Enriched)</button>
|
||||
<div class="result" id="status1Result"></div>
|
||||
<div class="context-list" id="status1List"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status 2: Prepare Prompt -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>3️⃣ Prepare Prompt <span class="status-indicator status-2">1 → 2</span></h3>
|
||||
<div class="selected-uuid" id="promptSelected"></div>
|
||||
<div class="form-group">
|
||||
<label>Context UUID *</label>
|
||||
<input type="text" id="promptUuid" placeholder="Paste UUID or click from list above">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Context Content *</label>
|
||||
<textarea id="promptContext" placeholder="Main context content"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Image Prompt * (JSON)</label>
|
||||
<textarea id="imgPrompt" placeholder='{"prompt": "description", "style": "cartoon"}'></textarea>
|
||||
<div class="small-note">Must be valid JSON object</div>
|
||||
</div>
|
||||
<button onclick="preparePrompt()">Prepare Prompt (Status 1 → 2)</button>
|
||||
<div class="result" id="promptResult"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 Get Contexts by Status <span class="status-indicator status-2">Status: 2</span></h3>
|
||||
<button onclick="getContextsByStatus(2)">Get Status 2 (Prompt Ready)</button>
|
||||
<div class="result" id="status2Result"></div>
|
||||
<div class="context-list" id="status2List"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status 3: Update Status -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>4️⃣ Update Status <span class="status-indicator status-3">2 → 3 or 1</span></h3>
|
||||
<div class="selected-uuid" id="updateStatusSelected"></div>
|
||||
<div class="form-group">
|
||||
<label>Context UUID *</label>
|
||||
<input type="text" id="updateStatusUuid" placeholder="Paste UUID or click from list above">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>New Status *</label>
|
||||
<select id="updateStatusValue">
|
||||
<option value="3">3 - Generating</option>
|
||||
<option value="1">1 - Back to Enriched</option>
|
||||
</select>
|
||||
</div>
|
||||
<button onclick="updateStatus()">Update Status</button>
|
||||
<div class="result" id="updateStatusResult"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 Get Contexts by Status <span class="status-indicator status-3">Status: 3</span></h3>
|
||||
<button onclick="getContextsByStatus(3)">Get Status 3 (Generating)</button>
|
||||
<div class="result" id="status3Result"></div>
|
||||
<div class="context-list" id="status3List"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status 4: Add Images -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>5️⃣ Add Images <span class="status-indicator status-4">3 → 4</span></h3>
|
||||
<div class="selected-uuid" id="imagesSelected"></div>
|
||||
<div class="form-group">
|
||||
<label>Context UUID *</label>
|
||||
<input type="text" id="imagesUuid" placeholder="Paste UUID or click from list above">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Image URLs * (JSON Array)</label>
|
||||
<textarea id="imageUrls" placeholder='["https://example.com/image1.jpg", "https://example.com/image2.jpg"]'></textarea>
|
||||
<div class="small-note">Must be valid JSON array of URLs</div>
|
||||
</div>
|
||||
<button onclick="addImages()">Add Images (Status 3 → 4)</button>
|
||||
<div class="result" id="imagesResult"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 Get Contexts by Status <span class="status-indicator status-4">Status: 4</span></h3>
|
||||
<button onclick="getContextsByStatus(4)">Get Status 4 (Image Ready)</button>
|
||||
<div class="result" id="status4Result"></div>
|
||||
<div class="context-list" id="status4List"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status 5: Approve -->
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>6️⃣ Approve Context <span class="status-indicator status-5">4 → 5</span></h3>
|
||||
<div class="selected-uuid" id="approveSelected"></div>
|
||||
<div class="form-group">
|
||||
<label>Context UUID *</label>
|
||||
<input type="text" id="approveUuid" placeholder="Paste UUID or click from list above">
|
||||
</div>
|
||||
<button onclick="approveContext()">Approve (Status 4 → 5)</button>
|
||||
<div class="result" id="approveResult"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 Get Contexts by Status <span class="status-indicator status-5">Status: 5</span></h3>
|
||||
<button onclick="getContextsByStatus(5)">Get Status 5 (Approved)</button>
|
||||
<div class="result" id="status5Result"></div>
|
||||
<div class="context-list" id="status5List"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const API_BASE = '/api/contexts';
|
||||
|
||||
function getHeaders() {
|
||||
return {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
}
|
||||
|
||||
function showResult(elementId, data, isSuccess = true) {
|
||||
const element = document.getElementById(elementId);
|
||||
element.className = `result show ${isSuccess ? 'success' : 'error'}`;
|
||||
element.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
|
||||
}
|
||||
|
||||
function copyToClipboard(text, button) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const originalText = button.textContent;
|
||||
button.textContent = '✓ Copied!';
|
||||
setTimeout(() => {
|
||||
button.textContent = originalText;
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function selectUuid(uuid, targetInputId, selectedDivId) {
|
||||
document.getElementById(targetInputId).value = uuid;
|
||||
const selectedDiv = document.getElementById(selectedDivId);
|
||||
selectedDiv.innerHTML = `<strong>Selected UUID:</strong> ${uuid}`;
|
||||
selectedDiv.className = 'selected-uuid show';
|
||||
|
||||
// Scroll to the input
|
||||
document.getElementById(targetInputId).scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
async function createContext() {
|
||||
const headers = getHeaders();
|
||||
|
||||
const title = document.getElementById('createTitle').value.trim();
|
||||
const desc = document.getElementById('createDesc').value.trim();
|
||||
const grade = document.getElementById('createGrade').value.trim();
|
||||
const type = document.getElementById('createType').value.trim();
|
||||
|
||||
if (!title || !desc || !grade) {
|
||||
showResult('createResult', { error: 'Title, desc, and grade are required' }, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(API_BASE, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify({ title, desc, grade: parseInt(grade), type })
|
||||
});
|
||||
const data = await response.json();
|
||||
showResult('createResult', data, response.ok);
|
||||
if (response.ok) {
|
||||
// Auto refresh status 0 list
|
||||
getContextsByStatus(0);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('createResult', { error: error.message }, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function getContextsByStatus(status) {
|
||||
const headers = getHeaders();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/status/${status}`, {
|
||||
headers: headers
|
||||
});
|
||||
const data = await response.json();
|
||||
showResult(`status${status}Result`, data, response.ok);
|
||||
|
||||
if (response.ok && data.data && data.data.contexts) {
|
||||
displayContextList(data.data.contexts, status);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult(`status${status}Result`, { error: error.message }, false);
|
||||
}
|
||||
}
|
||||
|
||||
function displayContextList(contexts, status) {
|
||||
const listElement = document.getElementById(`status${status}List`);
|
||||
if (contexts.length === 0) {
|
||||
listElement.innerHTML = '<p style="color: #666; font-style: italic;">No contexts found</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
listElement.innerHTML = contexts.map(ctx => `
|
||||
<div class="context-item">
|
||||
<strong>${ctx.title}</strong>
|
||||
<small>UUID: ${ctx.uuid} | Grade: ${ctx.grade} | Type: ${ctx.type}</small>
|
||||
<div class="quick-actions">
|
||||
<button class="copy-btn" onclick="copyToClipboard('${ctx.uuid}', this)">📋 Copy UUID</button>
|
||||
${getStatusActionButton(ctx, status)}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function getStatusActionButton(ctx, status) {
|
||||
const uuid = ctx.uuid;
|
||||
switch(status) {
|
||||
case 0:
|
||||
return `<button class="copy-btn" style="background: #17a2b8;" onclick="selectUuid('${uuid}', 'enrichUuid', 'enrichSelected')">→ Enrich</button>`;
|
||||
case 1:
|
||||
return `<button class="copy-btn" style="background: #17a2b8;" onclick="selectUuid('${uuid}', 'promptUuid', 'promptSelected')">→ Prepare Prompt</button>`;
|
||||
case 2:
|
||||
return `<button class="copy-btn" style="background: #17a2b8;" onclick="selectUuid('${uuid}', 'updateStatusUuid', 'updateStatusSelected')">→ Update Status</button>`;
|
||||
case 3:
|
||||
return `<button class="copy-btn" style="background: #17a2b8;" onclick="selectUuid('${uuid}', 'imagesUuid', 'imagesSelected')">→ Add Images</button>`;
|
||||
case 4:
|
||||
return `<button class="copy-btn" style="background: #17a2b8;" onclick="selectUuid('${uuid}', 'approveUuid', 'approveSelected')">→ Approve</button>`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
async function enrichContext() {
|
||||
const headers = getHeaders();
|
||||
|
||||
const uuid = document.getElementById('enrichUuid').value.trim();
|
||||
const knowledge = document.getElementById('enrichKnowledge').value.trim();
|
||||
|
||||
if (!uuid || !knowledge) {
|
||||
showResult('enrichResult', { error: 'UUID and knowledge are required' }, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/${uuid}/enrich`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify({ knowledge })
|
||||
});
|
||||
const data = await response.json();
|
||||
showResult('enrichResult', data, response.ok);
|
||||
if (response.ok) {
|
||||
getContextsByStatus(1);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('enrichResult', { error: error.message }, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function preparePrompt() {
|
||||
const headers = getHeaders();
|
||||
|
||||
const uuid = document.getElementById('promptUuid').value.trim();
|
||||
const context = document.getElementById('promptContext').value.trim();
|
||||
const imgPromptStr = document.getElementById('imgPrompt').value.trim();
|
||||
|
||||
if (!uuid || !context || !imgPromptStr) {
|
||||
showResult('promptResult', { error: 'UUID, context, and img_prompt are required' }, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const img_prompt = JSON.parse(imgPromptStr);
|
||||
const response = await fetch(`${API_BASE}/${uuid}/prepare-prompt`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify({ context, img_prompt })
|
||||
});
|
||||
const data = await response.json();
|
||||
showResult('promptResult', data, response.ok);
|
||||
if (response.ok) {
|
||||
getContextsByStatus(2);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('promptResult', { error: error.message }, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateStatus() {
|
||||
const headers = getHeaders();
|
||||
|
||||
const uuid = document.getElementById('updateStatusUuid').value.trim();
|
||||
const status = document.getElementById('updateStatusValue').value;
|
||||
|
||||
if (!uuid) {
|
||||
showResult('updateStatusResult', { error: 'UUID is required' }, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/${uuid}/update-status`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify({ status: parseInt(status) })
|
||||
});
|
||||
const data = await response.json();
|
||||
showResult('updateStatusResult', data, response.ok);
|
||||
if (response.ok) {
|
||||
getContextsByStatus(3);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('updateStatusResult', { error: error.message }, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function addImages() {
|
||||
const headers = getHeaders();
|
||||
|
||||
const uuid = document.getElementById('imagesUuid').value.trim();
|
||||
const imageUrlsStr = document.getElementById('imageUrls').value.trim();
|
||||
|
||||
if (!uuid || !imageUrlsStr) {
|
||||
showResult('imagesResult', { error: 'UUID and image URLs are required' }, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const image = JSON.parse(imageUrlsStr);
|
||||
if (!Array.isArray(image)) {
|
||||
throw new Error('Image URLs must be an array');
|
||||
}
|
||||
const response = await fetch(`${API_BASE}/${uuid}/add-images`, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: JSON.stringify({ image })
|
||||
});
|
||||
const data = await response.json();
|
||||
showResult('imagesResult', data, response.ok);
|
||||
if (response.ok) {
|
||||
getContextsByStatus(4);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('imagesResult', { error: error.message }, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function approveContext() {
|
||||
const headers = getHeaders();
|
||||
|
||||
const uuid = document.getElementById('approveUuid').value.trim();
|
||||
|
||||
if (!uuid) {
|
||||
showResult('approveResult', { error: 'UUID is required' }, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/${uuid}/approve`, {
|
||||
method: 'POST',
|
||||
headers: headers
|
||||
});
|
||||
const data = await response.json();
|
||||
showResult('approveResult', data, response.ok);
|
||||
if (response.ok) {
|
||||
getContextsByStatus(5);
|
||||
}
|
||||
} catch (error) {
|
||||
showResult('approveResult', { error: error.message }, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Page loaded
|
||||
window.addEventListener('load', () => {
|
||||
console.log('Context Workflow Manager loaded');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
27
rename-img-prompt.js
Normal file
27
rename-img-prompt.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const {sequelize} = require('./config/database');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ Database connected');
|
||||
|
||||
// Rename promptForImage to img_prompt
|
||||
await sequelize.query(`
|
||||
ALTER TABLE context
|
||||
CHANGE COLUMN promptForImage img_prompt JSON
|
||||
COMMENT 'Prompt configuration object'
|
||||
`);
|
||||
console.log('✅ Renamed promptForImage to img_prompt');
|
||||
|
||||
// Show final structure
|
||||
const [cols] = await sequelize.query('DESCRIBE context');
|
||||
console.log('\n📊 Final Context table structure:');
|
||||
cols.forEach((c, i) => console.log(` ${i+1}. ${c.Field} (${c.Type})`));
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
@@ -4,22 +4,41 @@ const contextController = require('../controllers/contextController');
|
||||
const { authenticateToken } = require('../middleware/auth');
|
||||
|
||||
/**
|
||||
* Context Routes
|
||||
* Context Routes - Workflow-based
|
||||
* Status: 0 (Draft) -> 1 (Enriched) -> 2 (Prompt Ready) -> 3 (Generating) -> 4 (Image Ready) -> 5 (Approved)
|
||||
*/
|
||||
|
||||
// Get all contexts
|
||||
router.get('/', authenticateToken, contextController.getAllContexts);
|
||||
// Create new context (Title, Desc, Grade) - Status 0
|
||||
router.post('/', contextController.createContext);
|
||||
|
||||
// Get contexts by status
|
||||
router.get('/status/:status', contextController.getContextsByStatus);
|
||||
|
||||
// Status 0 -> 1: Update knowledge
|
||||
router.post('/:id/enrich', contextController.enrichContext);
|
||||
|
||||
// Status 1 -> 2: Update context and img_prompt
|
||||
router.post('/:id/prepare-prompt', contextController.preparePrompt);
|
||||
|
||||
// Status 2 -> 3 or 1: Update status
|
||||
router.post('/:id/update-status', contextController.updateStatusFromPromptReady);
|
||||
|
||||
// Status 3 -> 4: Add images
|
||||
router.post('/:id/add-images', contextController.addImages);
|
||||
|
||||
// Status 4 -> 5: Approve
|
||||
router.post('/:id/approve', contextController.approveContext);
|
||||
|
||||
// Get all contexts (with optional filters)
|
||||
router.get('/', contextController.getAllContexts);
|
||||
|
||||
// Get context by UUID
|
||||
router.get('/:id', authenticateToken, contextController.getContextById);
|
||||
router.get('/:id', contextController.getContextById);
|
||||
|
||||
// Create context
|
||||
router.post('/', authenticateToken, contextController.createContext);
|
||||
|
||||
// Update context
|
||||
router.put('/:id', authenticateToken, contextController.updateContext);
|
||||
// Update context (general - use with caution)
|
||||
router.put('/:id', contextController.updateContext);
|
||||
|
||||
// Delete context
|
||||
router.delete('/:id', authenticateToken, contextController.deleteContext);
|
||||
router.delete('/:id', contextController.deleteContext);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
45
sync-context-alter.js
Normal file
45
sync-context-alter.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Sync Context model to database with ALTER
|
||||
* This will update the existing table structure
|
||||
*/
|
||||
const { sequelize } = require('./config/database');
|
||||
const Context = require('./models/Context');
|
||||
|
||||
async function syncContext() {
|
||||
try {
|
||||
console.log('🔄 Starting Context table sync with ALTER...');
|
||||
|
||||
// Test connection
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ Database connection OK');
|
||||
|
||||
// Sync Context model with alter to update structure
|
||||
await Context.sync({ alter: true });
|
||||
console.log('✅ Context table synced successfully (grade column updated to INTEGER)');
|
||||
|
||||
// Show table structure
|
||||
const [columns] = await sequelize.query(`
|
||||
SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_NAME = 'context' AND TABLE_SCHEMA = DATABASE()
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`);
|
||||
|
||||
console.log('\n📊 Context table structure:');
|
||||
columns.forEach((col, index) => {
|
||||
console.log(` ${index + 1}. ${col.COLUMN_NAME} - ${col.COLUMN_TYPE} ${col.IS_NULLABLE === 'NO' ? 'NOT NULL' : 'NULL'}`);
|
||||
if (col.COLUMN_COMMENT) {
|
||||
console.log(` Comment: ${col.COLUMN_COMMENT}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\n✅ Context table update complete!');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Error syncing Context table:', error.message);
|
||||
console.error(error.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
syncContext();
|
||||
@@ -17,8 +17,8 @@ async function syncDatabase() {
|
||||
setupRelationships();
|
||||
console.log('✅ Model relationships configured');
|
||||
|
||||
// Sync all models (creates tables if not exist)
|
||||
await sequelize.sync({ alter: false, force: false });
|
||||
// Sync all models (creates tables if not exist, alters existing tables)
|
||||
await sequelize.sync({ alter: true, force: false });
|
||||
console.log('✅ All models synced successfully');
|
||||
|
||||
// Show created tables
|
||||
|
||||
Reference in New Issue
Block a user