Files
sena_db_api_layer/public/contexts.html
silverpro89 b7ba1d02b3
All checks were successful
Deploy to Production / deploy (push) Successful in 20s
update
2026-02-18 18:01:45 +07:00

747 lines
27 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>
<hr style="margin: 15px 0; border: 1px solid #e0e0e0;">
<button onclick="bulkUpdateStatus2To3()" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); margin-top: 10px;">
🚀 Quick Approve: All Status 2 → 3
</button>
<div class="result" id="bulkUpdateResultTop"></div>
<hr style="margin: 15px 0; border: 1px solid #e0e0e0;">
<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>
<hr style="margin: 20px 0; border: 1px solid #e0e0e0;">
<h4 style="color: #667eea; margin-bottom: 10px;">🚀 Bulk Update</h4>
<button onclick="bulkUpdateStatus2To3()" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
Update All Status 2 → 3
</button>
<div class="result" id="bulkUpdateResult"></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 URL * (String)</label>
<input type="text" id="imageUrls" placeholder="https://example.com/image.jpg">
<div class="small-note">Enter a single image URL</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 bulkUpdateStatus2To3() {
const headers = getHeaders();
if (!confirm('Are you sure you want to update ALL contexts from status 2 to status 3?')) {
return;
}
try {
const response = await fetch(`${API_BASE}/bulk/status-2-to-3`, {
method: 'POST',
headers: headers
});
const data = await response.json();
showResult('bulkUpdateResult', data, response.ok);
showResult('bulkUpdateResultTop', data, response.ok);
if (response.ok) {
getContextsByStatus(3);
getContextsByStatus(2);
}
} catch (error) {
showResult('bulkUpdateResult', { error: error.message }, false);
showResult('bulkUpdateResultTop', { error: error.message }, false);
}
}
async function addImages() {
const headers = getHeaders();
const uuid = document.getElementById('imagesUuid').value.trim();
const imageUrl = document.getElementById('imageUrls').value.trim();
if (!uuid || !imageUrl) {
showResult('imagesResult', { error: 'UUID and image URL are required' }, false);
return;
}
try {
const response = await fetch(`${API_BASE}/${uuid}/add-images`, {
method: 'POST',
headers: headers,
body: JSON.stringify({ image: imageUrl })
});
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>