Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions qc_viewer/routers/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,8 +355,18 @@ async def refine_explanation(request: RefineRequest):
# For refinement, we want a more capable model
routing_engine.config.model_routing.generation = "openai/gpt-4o"
refined = routing_engine.generate_content(prompt, task_type="generation")
refined_question = dict(request.original_q)
original_generated = refined_question.get("generated_content")
generated_content = (
dict(original_generated)
if isinstance(original_generated, dict)
else {}
)
generated_content["detailed_explanation"] = refined
refined_question["generated_content"] = generated_content
return {
"explanation": refined,
"refined_question": refined_question,
"model": routing_engine.config.model_routing.generation,
"timestamp": datetime.now().isoformat(),
}
Expand Down
1 change: 1 addition & 0 deletions qc_viewer/static/js/automate_ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const AutomationUI = {
const resp = await fetch('/api/automate/config');
if (!resp.ok) throw new Error('Failed to fetch config');
const config = await resp.json();
this.pipelineConfig = config;

if (config.workspace) {
const curSelect = document.getElementById('curriculumSelect');
Expand Down
52 changes: 34 additions & 18 deletions qc_viewer/static/js/controllers/drafts.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { AutomationAPI } from '../automate_api.js';

const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[char]));

const statusClassName = (status) => String(status || 'unknown')
.toLowerCase()
.replace(/[^a-z0-9_-]/g, '') || 'unknown';

export const DraftController = {
activeStreams: {},

Expand All @@ -12,8 +24,8 @@ export const DraftController = {
text.textContent = `Uploading ${file.name}...`;

try {
const subject = document.getElementById('curriculumSelect')?.value || 'General';
const paperCode = document.getElementById('targetTableSelect')?.value || '';
const subject = this.pipelineConfig?.workspace?.default_subject || 'General';
const paperCode = file.name.replace(/\.[^/.]+$/, '').trim() || file.name;

// Upload the file
const result = await AutomationAPI.uploadPDF(file, subject, paperCode);
Expand All @@ -27,9 +39,12 @@ export const DraftController = {

// IMMEDIATE HANDOVER: Inject the card right away without waiting for any fetches
if (result && result.id) {
const safeResultId = escapeHtml(result.id);
const handoverState = {
id: result.id,
filename: file.name,
subject,
paper_code: paperCode,
status: 'PROCESSING',
progress: 10,
status_message: 'Initializing AI pipeline...',
Expand All @@ -48,10 +63,10 @@ export const DraftController = {
// Prepend the new card HTML
const dateStr = 'Just now';
const newCardHTML = `
<div class="draft-card processing-active" data-id="${result.id}">
<div class="draft-card processing-active" data-id="${safeResultId}">
<div class="draft-info">
<div style="display:flex; align-items:center; gap:10px; margin-bottom:4px;">
<h3 style="margin:0;">${file.name}</h3>
<h3 style="margin:0;">${escapeHtml(file.name)}</h3>
</div>
<p>
<span>${dateStr}</span>
Expand All @@ -67,9 +82,9 @@ export const DraftController = {
<div class="action-buttons">
<div style="display:flex; align-items:center; gap:8px;">
<span class="loader" style="width:16px; height:16px;"></span>
<button class="btn btn-outline btn-sm btn-stop" data-id="${result.id}" style="border:1px solid rgba(239, 68, 68, 0.3); color:var(--danger)">Stop</button>
<button class="btn btn-outline btn-sm btn-stop" data-id="${safeResultId}" style="border:1px solid rgba(239, 68, 68, 0.3); color:var(--danger)">Stop</button>
</div>
<button type="button" class="btn btn-outline btn-sm btn-delete" data-id="${result.id}" style="border:1px solid rgba(239,68,68,0.3); color:var(--danger)">Delete</button>
<button type="button" class="btn btn-outline btn-sm btn-delete" data-id="${safeResultId}" style="border:1px solid rgba(239,68,68,0.3); color:var(--danger)">Delete</button>
</div>
</div>
</div>`;
Expand Down Expand Up @@ -127,6 +142,7 @@ export const DraftController = {
let hasProcessing = false;
draftList.innerHTML = drafts.map(d => {
const status = d.status || 'UNKNOWN';
const statusClass = statusClassName(status);
const isProcessing = status === 'PROCESSING' || status === 'EXTRACTING';
if (isProcessing) hasProcessing = true;

Expand All @@ -150,15 +166,15 @@ export const DraftController = {
const statusMsg = d.status_message || (isProcessing ? 'Processing...' : '');

return `
<div class="draft-card ${isProcessing ? 'processing-active' : ''}" data-id="${d.id}">
<div class="draft-card ${isProcessing ? 'processing-active' : ''}" data-id="${escapeHtml(d.id)}">
<div class="draft-info">
<div style="display:flex; align-items:center; gap:10px; margin-bottom:4px;">
<h3 style="margin:0;">${d.filename || 'Untitled Document'}</h3>
<h3 style="margin:0;">${escapeHtml(d.filename || 'Untitled Document')}</h3>
${qCount > 0 ? `<span class="question-count-badge">${qCount} Questions</span>` : ''}
</div>
<p>
<span>${dateStr}${reviewedStr}${timeStr}</span>
<span class="status-message-streaming" style="font-size:0.75rem;">${statusMsg ? ` • ${statusMsg}` : ''}</span>
<span>${escapeHtml(`${dateStr}${reviewedStr}${timeStr}`)}</span>
<span class="status-message-streaming" style="font-size:0.75rem;">${statusMsg ? ` • ${escapeHtml(statusMsg)}` : ''}</span>
</p>
${isProcessing ? `
<div class="mini-progress-container" style="width: 200px; height: 4px; background: #334155; border-radius: 2px; margin-top: 8px; overflow: hidden;">
Expand All @@ -168,10 +184,10 @@ export const DraftController = {
` : ''}
</div>
<div style="display: flex; align-items: center; gap: 16px;">
<span class="status-badge status-${status.toLowerCase()}">${status}</span>
<span class="status-badge status-${statusClass}">${escapeHtml(status)}</span>
<div class="action-buttons">
${this.renderActionButton(d)}
<button type="button" class="btn btn-outline btn-sm btn-delete" data-id="${d.id}" style="border:1px solid rgba(239,68,68,0.3); color:var(--danger)">Delete</button>
<button type="button" class="btn btn-outline btn-sm btn-delete" data-id="${escapeHtml(d.id)}" style="border:1px solid rgba(239,68,68,0.3); color:var(--danger)">Delete</button>
</div>
</div>
</div>
Expand All @@ -183,7 +199,7 @@ export const DraftController = {
if (e.target.closest('.action-buttons')) return;
const id = card.dataset.id;
const d = drafts.find(x => x.id === id);
if (d.status === 'PROCESSED' || d.status === 'REVIEW_READY') this.openReview(id);
if (d && (d.status === 'PROCESSED' || d.status === 'REVIEW_READY')) this.openReview(id);
};
});

Expand Down Expand Up @@ -291,7 +307,7 @@ export const DraftController = {
const badgeEl = card.querySelector('.status-badge');
if (badgeEl) {
badgeEl.textContent = status;
badgeEl.className = `status-badge status-${String(status).toLowerCase()}`;
badgeEl.className = `status-badge status-${statusClassName(status)}`;
}
const mini = card.querySelector('.mini-progress-container');
const pctEl = card.querySelector('.draft-progress-pct')
Expand All @@ -302,7 +318,7 @@ export const DraftController = {
const actions = card.querySelector('.action-buttons');
if (actions && typeof this.renderActionButton === 'function') {
const id = d.id;
actions.innerHTML = `${this.renderActionButton(d)}<button type="button" class="btn btn-outline btn-sm btn-delete" data-id="${id}" style="border:1px solid rgba(239,68,68,0.3); color:var(--danger)">Delete</button>`;
actions.innerHTML = `${this.renderActionButton(d)}<button type="button" class="btn btn-outline btn-sm btn-delete" data-id="${escapeHtml(id)}" style="border:1px solid rgba(239,68,68,0.3); color:var(--danger)">Delete</button>`;
actions.querySelector('.btn-delete')?.addEventListener('click', (e) => {
e.stopPropagation();
this.deleteDraft(id);
Expand Down Expand Up @@ -334,16 +350,16 @@ export const DraftController = {
renderActionButton(d) {
if (d.status === 'PROCESSED' || d.status === 'REVIEW_READY') {
return `<div style="display:flex; align-items:center; gap:8px;">
<button type="button" class="btn btn-outline btn-sm btn-review" data-id="${d.id}">Review</button>
<button type="button" class="btn btn-outline btn-sm btn-card-export" data-id="${d.id}" title="Choose export format">📥 Export ▾</button>
<button type="button" class="btn btn-outline btn-sm btn-review" data-id="${escapeHtml(d.id)}">Review</button>
<button type="button" class="btn btn-outline btn-sm btn-card-export" data-id="${escapeHtml(d.id)}" title="Choose export format">📥 Export ▾</button>
</div>`;
} else if (d.status === 'FAILED') {
return `<span style="color:var(--danger); font-size:0.8rem;">Error</span>`;
} else if (d.status === 'PROCESSING' || d.status === 'EXTRACTING') {
return `
<div style="display:flex; align-items:center; gap:8px;">
<span class="loader" style="width:16px; height:16px;"></span>
<button class="btn btn-outline btn-sm btn-stop" data-id="${d.id}" style="border:1px solid rgba(239, 68, 68, 0.3); color:var(--danger)">Stop</button>
<button class="btn btn-outline btn-sm btn-stop" data-id="${escapeHtml(d.id)}" style="border:1px solid rgba(239, 68, 68, 0.3); color:var(--danger)">Stop</button>
</div>
`;
} else {
Expand Down
Loading
Loading