update context API
All checks were successful
Deploy to Production / deploy (push) Successful in 19s

This commit is contained in:
silverpro89
2026-02-06 11:28:06 +07:00
parent 97dbbd4d12
commit aaba22b40c
12 changed files with 1375 additions and 49 deletions

708
public/contexts.html Normal file
View 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>