|
|
@@ -1652,6 +1652,58 @@ function setupEventListeners() {
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
+ const btnUpdateSchema = document.getElementById('update-schema-btn');
|
|
|
+ if (btnUpdateSchema) {
|
|
|
+ btnUpdateSchema.addEventListener('click', async () => {
|
|
|
+ if (!currentPromptName) return;
|
|
|
+ const originalText = btnUpdateSchema.innerHTML;
|
|
|
+ btnUpdateSchema.innerHTML = '<span style="font-size: 1.1em;">⏳</span> 更新中...';
|
|
|
+ btnUpdateSchema.disabled = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ // First save the prompt so the backend reads the latest content
|
|
|
+ const reqBody = { content: elPromptTextarea.value };
|
|
|
+ if (elSchemaTextarea) reqBody.schema_content = elSchemaTextarea.value;
|
|
|
+
|
|
|
+ let res = await fetch(`/api/prompts/${currentPromptName}`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ body: JSON.stringify(reqBody)
|
|
|
+ });
|
|
|
+ if (!res.ok) {
|
|
|
+ const errData = await res.json();
|
|
|
+ throw new Error(errData.detail || "保存 Prompt 失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Then call the schema update API
|
|
|
+ res = await fetch(`/api/prompts/${currentPromptName}/update_schema`, {
|
|
|
+ method: 'POST'
|
|
|
+ });
|
|
|
+
|
|
|
+ if (res.ok) {
|
|
|
+ const data = await res.json();
|
|
|
+ if (elSchemaTextarea && data.schema_content) {
|
|
|
+ elSchemaTextarea.value = data.schema_content;
|
|
|
+ }
|
|
|
+ btnUpdateSchema.innerHTML = '<span style="font-size: 1.1em;">✅</span> 更新成功';
|
|
|
+ setTimeout(() => {
|
|
|
+ btnUpdateSchema.innerHTML = originalText;
|
|
|
+ btnUpdateSchema.disabled = false;
|
|
|
+ }, 2000);
|
|
|
+ } else {
|
|
|
+ const errData = await res.json();
|
|
|
+ throw new Error(errData.detail || "更新 Schema 失败");
|
|
|
+ }
|
|
|
+ } catch(e) {
|
|
|
+ alert(e.message || '更新失败');
|
|
|
+ btnUpdateSchema.innerHTML = '<span style="font-size: 1.1em;">❌</span> 失败';
|
|
|
+ setTimeout(() => {
|
|
|
+ btnUpdateSchema.innerHTML = originalText;
|
|
|
+ btnUpdateSchema.disabled = false;
|
|
|
+ }, 2000);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// Boot
|
|
|
@@ -2170,14 +2222,26 @@ window.renderStructuredData = function(items, type, parentItem = null) {
|
|
|
} else if (item.method && !item.method.includes('[')) {
|
|
|
actionStr = item.method;
|
|
|
} else if (item.steps && Array.isArray(item.steps)) {
|
|
|
- actionStr = item.steps.map(s => {
|
|
|
- if (s.action && s.action.main_action) {
|
|
|
- return s.action.mechanism ? `[${s.action.main_action}] ${s.action.mechanism}` : s.action.main_action;
|
|
|
- }
|
|
|
- return '未知';
|
|
|
- }).join(' ➔ ');
|
|
|
+ const hasAnyValidIO = item.steps.some(s => hasValidIO(s.inputs) || hasValidIO(s.outputs));
|
|
|
+ if (hasAnyValidIO) {
|
|
|
+ actionStr = item.steps.map(s => {
|
|
|
+ if (s.action && s.action.main_action) {
|
|
|
+ return s.action.mechanism ? `[${s.action.main_action}] ${s.action.mechanism}` : s.action.main_action;
|
|
|
+ }
|
|
|
+ if (s.method) return s.method;
|
|
|
+ if (s.phase) return s.phase;
|
|
|
+ return '未知';
|
|
|
+ }).join(' ➔ ');
|
|
|
+ } else {
|
|
|
+ actionStr = item.method || item.name || type === 'workflow' ? '工作流' : `节点 ${idx + 1}`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (hasValidIO(item.inputs) || hasValidIO(item.outputs)) {
|
|
|
+ title = buildFullTitle(item.inputs, item.outputs, actionStr, item.method || item.name || `节点 ${idx + 1}`);
|
|
|
+ } else {
|
|
|
+ title = String(actionStr).replace(/</g, '<').replace(/>/g, '>');
|
|
|
}
|
|
|
- title = buildFullTitle(item.inputs, item.outputs, actionStr, item.method || item.name || `节点 ${idx + 1}`);
|
|
|
} else {
|
|
|
const escapeHtml = (s) => String(s).replace(/</g, '<').replace(/>/g, '>');
|
|
|
title = escapeHtml(item.method || item.name || '');
|
|
|
@@ -2519,6 +2583,21 @@ window.renderStructuredData = function(items, type, parentItem = null) {
|
|
|
|
|
|
// Render steps array specially
|
|
|
if (item.steps && Array.isArray(item.steps)) {
|
|
|
+ const allFragments = (parentItem && parentItem.fragments) || [];
|
|
|
+
|
|
|
+ const hasInputs = item.steps.some(s => s.inputs && Array.isArray(s.inputs) && s.inputs.length > 0) || allFragments.some(f => f.inputs && Array.isArray(f.inputs) && f.inputs.length > 0);
|
|
|
+ const hasOutputs = item.steps.some(s => s.outputs && Array.isArray(s.outputs) && s.outputs.length > 0) || allFragments.some(f => f.outputs && Array.isArray(f.outputs) && f.outputs.length > 0);
|
|
|
+ const hasAction = item.steps.some(s => s.action || s.method || s.description) || allFragments.some(f => f.action);
|
|
|
+ const hasApplyTo = item.steps.some(s => s.apply_to || s.apply_to_draft || s.apply_to_grounding) || allFragments.some(f => f.apply_to || f.apply_to_draft || f.apply_to_grounding);
|
|
|
+ const hasTools = item.steps.some(s => s.tools && Array.isArray(s.tools) && s.tools.length > 0) || allFragments.some(f => f.tools && Array.isArray(f.tools) && f.tools.length > 0);
|
|
|
+ const hasRelation = item.steps.some(s => s.relation);
|
|
|
+ const hasStepId = item.steps.some(s => s.step_id);
|
|
|
+
|
|
|
+ let minWidth = 400;
|
|
|
+ if (hasStepId) minWidth += 60;
|
|
|
+ if (hasRelation) minWidth += 120;
|
|
|
+ minWidth += 400; // base width for Fragments column
|
|
|
+
|
|
|
html += `<div class="structured-row">
|
|
|
<div class="structured-label">steps</div>
|
|
|
<div class="structured-value" style="width: 100%; overflow-x: auto; padding-bottom: 8px;">
|
|
|
@@ -2530,95 +2609,134 @@ window.renderStructuredData = function(items, type, parentItem = null) {
|
|
|
.steps-table tr.expanded-row .expand-icon { transform: rotate(90deg); }
|
|
|
.cell-fade { position: absolute; bottom: 0; left: 0; right: 0; height: 30px; background: linear-gradient(to bottom, rgba(255,255,255,0), white); pointer-events: none; }
|
|
|
.steps-table tr.expanded-row .cell-fade { display: none; }
|
|
|
+ .steps-table td { border-bottom: 1px solid rgba(0,0,0,0.05); }
|
|
|
+ .steps-table tr.expanded-row .fragments-collapsed-view { display: none !important; }
|
|
|
+ .steps-table tr.expanded-row .fragments-expanded-view { display: flex !important; }
|
|
|
</style>
|
|
|
- <table class="steps-table" style="width: 100%; min-width: 1200px; border-collapse: collapse; margin-top: 8px; font-size: 0.9em; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
|
|
|
+ <table class="steps-table" style="width: 100%; min-width: ${minWidth}px; border-collapse: collapse; margin-top: 8px; font-size: 0.9em; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
|
|
|
<thead>
|
|
|
<tr style="background: rgba(0,0,0,0.03); border-bottom: 2px solid rgba(0,0,0,0.1); text-align: left;">
|
|
|
<th style="padding: 12px 10px; width: 60px;">序号</th>
|
|
|
+ ${hasStepId ? `<th style="padding: 12px 10px; width: 60px;">ID</th>` : ''}
|
|
|
<th style="padding: 12px 10px; width: 70px;">阶段</th>
|
|
|
- <th style="display: none;">操作流</th>
|
|
|
- <th style="padding: 12px 10px; width: 200px;">输入</th>
|
|
|
- <th style="padding: 12px 10px; width: 120px;">动作</th>
|
|
|
- <th style="padding: 12px 10px; width: 200px;">输出</th>
|
|
|
- <th style="padding: 12px 10px; width: 200px;">作用域</th>
|
|
|
- <th style="padding: 12px 10px; width: 280px;">做法</th>
|
|
|
- <th style="padding: 12px 10px; width: 100px;">工具</th>
|
|
|
+ ${hasRelation ? `<th style="padding: 12px 10px; width: 120px;">流转关系</th>` : ''}
|
|
|
+ <th style="padding: 12px 10px; width: 280px; border-right: 1px solid rgba(0,0,0,0.05);">做法</th>
|
|
|
+ <th style="padding: 12px 10px; width: auto;">原子操作 (Fragments)</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody>`;
|
|
|
|
|
|
const escapeHtml = (s) => String(s).replace(/</g, '<').replace(/>/g, '>');
|
|
|
item.steps.forEach((step, stepIdx) => {
|
|
|
- let actionText = '未知';
|
|
|
- if (step.action && step.action.main_action) {
|
|
|
- actionText = step.action.mechanism ? `[${step.action.main_action}] ${step.action.mechanism}` : step.action.main_action;
|
|
|
- } else if (step.method) {
|
|
|
- actionText = step.method;
|
|
|
- } else if (step.description) {
|
|
|
- actionText = step.description;
|
|
|
- }
|
|
|
+ const stepFragments = allFragments.filter(f => f.fragment_id && (f.fragment_id === `f_${step.step_id}` || f.fragment_id.startsWith(`f_${step.step_id}_`)));
|
|
|
|
|
|
- let stepTitle = '';
|
|
|
- const hasValidIO = (arr) => Array.isArray(arr) && arr.length > 0 && (arr[0].role || arr[0].description);
|
|
|
- if (hasValidIO(step.inputs) || hasValidIO(step.outputs)) {
|
|
|
- stepTitle = buildFullTitle(step.inputs, step.outputs, actionText, step.method || step.description || `步骤 ${step.order || stepIdx + 1}`);
|
|
|
- } else {
|
|
|
- stepTitle = escapeHtml(step.method || step.description || '');
|
|
|
- if (!stepTitle) stepTitle = escapeHtml(actionText);
|
|
|
- if (!stepTitle || stepTitle === '未知') stepTitle = escapeHtml(`步骤 ${step.order || stepIdx + 1}`);
|
|
|
+ let fragsToRender = stepFragments.length > 0 ? stepFragments : [step];
|
|
|
+ if (stepFragments.length === 0) {
|
|
|
+ const hasFragFields = step.inputs || step.outputs || step.action || step.tools || step.apply_to || step.apply_to_draft || step.apply_to_grounding;
|
|
|
+ if (!hasFragFields) fragsToRender = [];
|
|
|
}
|
|
|
|
|
|
- let actionHtml = escapeHtml(actionText);
|
|
|
- if (step.action && step.action.main_action) {
|
|
|
- const badgeHtml = `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${escapeHtml(step.action.main_action)}</span>`;
|
|
|
- actionHtml = step.action.mechanism ? badgeHtml + escapeHtml(step.action.mechanism) : badgeHtml;
|
|
|
+ let fragmentsHtml = '';
|
|
|
+ if (fragsToRender.length > 0) {
|
|
|
+ let collapsedIdsHtml = fragsToRender.map(src => {
|
|
|
+ return src.fragment_id
|
|
|
+ ? `<span style="display: inline-block; font-family: monospace; font-size: 0.85em; font-weight: bold; color: #4338ca; background: #e0e7ff; border: 1px solid #c7d2fe; padding: 2px 6px; border-radius: 4px;">${escapeHtml(src.fragment_id)}</span>`
|
|
|
+ : `<span style="display: inline-block; font-size: 0.85em; color: #64748b; background: #f1f5f9; padding: 2px 6px; border-radius: 4px;">含操作详情</span>`;
|
|
|
+ }).join(' ');
|
|
|
+
|
|
|
+ let expandedCardsHtml = fragsToRender.map(src => {
|
|
|
+ let actionText = '未知';
|
|
|
+ if (src.action && src.action.main_action) {
|
|
|
+ actionText = src.action.mechanism ? `[${src.action.main_action}] ${src.action.mechanism}` : src.action.main_action;
|
|
|
+ } else if (src.method) {
|
|
|
+ actionText = src.method;
|
|
|
+ } else if (src.description) {
|
|
|
+ actionText = src.description;
|
|
|
+ }
|
|
|
+
|
|
|
+ let actionHtml = escapeHtml(actionText);
|
|
|
+ if (src.action && src.action.main_action) {
|
|
|
+ const badgeHtml = `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${escapeHtml(src.action.main_action)}</span>`;
|
|
|
+ actionHtml = src.action.mechanism ? badgeHtml + escapeHtml(src.action.mechanism) : badgeHtml;
|
|
|
+ } else {
|
|
|
+ const match = actionText.match(/^\[(.*?)\]\s*(.*)$/);
|
|
|
+ if (match) {
|
|
|
+ actionHtml = `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${escapeHtml(match[1])}</span>${escapeHtml(match[2])}`;
|
|
|
+ } else if (actionText === '未知') {
|
|
|
+ actionHtml = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let toolsHtml = '';
|
|
|
+ if (src.tools && src.tools.length > 0) {
|
|
|
+ toolsHtml = src.tools.map(t => `<span class="structured-badge tool-badge" style="display:inline-block; margin:2px;">${escapeHtml(t)}</span>`).join('');
|
|
|
+ }
|
|
|
+
|
|
|
+ return `<div style="border: 1px solid rgba(0,0,0,0.08); border-radius: 6px; background: white; position: relative; box-shadow: 0 1px 2px rgba(0,0,0,0.02); overflow: hidden;">
|
|
|
+ <div style="display: flex; justify-content: space-between; align-items: center; background: #f8fafc; padding: 8px 12px; border-bottom: 1px solid rgba(0,0,0,0.05);">
|
|
|
+ <div style="font-weight: bold; color: var(--text-main); font-size: 0.95em;">${actionHtml || '操作'}</div>
|
|
|
+ ${src.fragment_id ? `<div style="font-family: monospace; font-size: 0.85em; font-weight: bold; color: #4338ca; background: #e0e7ff; border: 1px solid #c7d2fe; padding: 2px 8px; border-radius: 4px;">${escapeHtml(src.fragment_id)}</div>` : ''}
|
|
|
+ </div>
|
|
|
+ <div style="padding: 10px 12px; display: flex; flex-direction: column; gap: 8px;">
|
|
|
+ ${src.inputs && src.inputs.length > 0 ? `
|
|
|
+ <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
+ <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">输入</div>
|
|
|
+ <div style="flex-grow: 1; min-width: 0;">${renderDataObjList(src.inputs)}</div>
|
|
|
+ </div>` : ''}
|
|
|
+
|
|
|
+ ${src.outputs && src.outputs.length > 0 ? `
|
|
|
+ <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
+ <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">输出</div>
|
|
|
+ <div style="flex-grow: 1; min-width: 0;">${renderDataObjList(src.outputs)}</div>
|
|
|
+ </div>` : ''}
|
|
|
+
|
|
|
+ ${(src.apply_to_draft || src.apply_to_grounding || src.apply_to) ? `
|
|
|
+ <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
+ <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">作用域</div>
|
|
|
+ <div style="flex-grow: 1; min-width: 0; font-size: 0.9em; line-height: 1.5; color: var(--text-main); margin-top: 2px;">${renderApplyToVal(src.apply_to_draft || src.apply_to_grounding || src.apply_to)}</div>
|
|
|
+ </div>` : ''}
|
|
|
+
|
|
|
+ ${toolsHtml ? `
|
|
|
+ <div style="display: flex; align-items: flex-start; gap: 12px;">
|
|
|
+ <div style="width: 48px; font-size: 0.8em; color: var(--text-muted); background: #f1f5f9; padding: 3px 6px; border-radius: 4px; text-align: center; flex-shrink: 0; margin-top: 1px;">工具</div>
|
|
|
+ <div style="flex-grow: 1; min-width: 0; display: flex; flex-wrap: wrap; gap: 4px;">${toolsHtml}</div>
|
|
|
+ </div>` : ''}
|
|
|
+ </div>
|
|
|
+ </div>`;
|
|
|
+ }).join('');
|
|
|
+
|
|
|
+ fragmentsHtml = `
|
|
|
+ <div class="fragments-collapsed-view" style="display: flex; gap: 6px; flex-wrap: wrap; align-items: center; padding-top: 2px;">
|
|
|
+ ${collapsedIdsHtml}
|
|
|
+ </div>
|
|
|
+ <div class="fragments-expanded-view" style="display: none; flex-direction: column; gap: 8px;">
|
|
|
+ ${expandedCardsHtml}
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
} else {
|
|
|
- const match = actionText.match(/^\[(.*?)\]\s*(.*)$/);
|
|
|
- if (match) {
|
|
|
- actionHtml = `<span class="data-type-badge" style="background:#e0e7ff;color:#3730a3;font-weight:normal;margin-right:6px;margin-bottom:2px;display:inline-block;">${escapeHtml(match[1])}</span>${escapeHtml(match[2])}`;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let toolsHtml = '';
|
|
|
- if (step.tools && step.tools.length > 0) {
|
|
|
- toolsHtml = step.tools.map(t => `<span class="structured-badge tool-badge" style="display:inline-block; margin:2px;">${escapeHtml(t)}</span>`).join('');
|
|
|
+ fragmentsHtml = `<div class="cell-content" style="max-height: 50px; overflow: hidden; color: var(--text-muted);">-</div><div class="cell-fade"></div>`;
|
|
|
}
|
|
|
|
|
|
html += `
|
|
|
- <tr style="border-bottom: 1px solid rgba(0,0,0,0.05); vertical-align: top;" onclick="this.classList.toggle('expanded-row')">
|
|
|
- <td style="padding: 14px 10px; font-weight: 500; color: var(--text-muted); text-align: center;">
|
|
|
+ <tr style="vertical-align: top; border-bottom: 1px solid rgba(0,0,0,0.05);" onclick="this.classList.toggle('expanded-row')">
|
|
|
+ <td style="padding: 14px 10px; font-weight: 500; color: var(--text-muted); text-align: center; border-right: 1px dashed rgba(0,0,0,0.05);">
|
|
|
<span class="expand-icon">▶</span> ${step.order || stepIdx + 1}
|
|
|
</td>
|
|
|
- <td style="padding: 14px 10px;">
|
|
|
+ ${hasStepId ? `<td style="padding: 14px 10px; font-family: monospace; color: var(--text-muted); border-right: 1px dashed rgba(0,0,0,0.05);">${escapeHtml(step.step_id || '-')}</td>` : ''}
|
|
|
+ <td style="padding: 14px 10px; border-right: 1px dashed rgba(0,0,0,0.05);">
|
|
|
${step.phase ? `<span class="structured-badge" style="background:#f1f5f9; color:#475569; font-weight: 500;">${escapeHtml(step.phase)}</span>` : '-'}
|
|
|
</td>
|
|
|
- <td style="display: none;">
|
|
|
- <div style="font-weight: 600; color: var(--text-secondary); font-size: 0.9em; background: rgba(0,0,0,0.03); padding: 4px 8px; border-radius: 4px; display: inline-block; word-break: break-all;">
|
|
|
- ${stepTitle}
|
|
|
- </div>
|
|
|
- </td>
|
|
|
- <td style="padding: 14px 10px; position: relative;">
|
|
|
- <div class="cell-content" style="max-height: 50px; overflow: hidden;">${step.inputs && Array.isArray(step.inputs) && step.inputs.length > 0 ? renderDataObjList(step.inputs) : '-'}</div>
|
|
|
- <div class="cell-fade"></div>
|
|
|
- </td>
|
|
|
- <td style="padding: 14px 10px; font-weight: bold; color: var(--text-main); line-height: 1.5;">
|
|
|
- ${actionHtml}
|
|
|
- </td>
|
|
|
- <td style="padding: 14px 10px; position: relative;">
|
|
|
- <div class="cell-content" style="max-height: 50px; overflow: hidden;">${step.outputs && Array.isArray(step.outputs) && step.outputs.length > 0 ? renderDataObjList(step.outputs) : '-'}</div>
|
|
|
- <div class="cell-fade"></div>
|
|
|
- </td>
|
|
|
- <td style="padding: 14px 10px; position: relative;">
|
|
|
- <div class="cell-content" style="max-height: 50px; overflow: hidden;">${renderApplyToVal(step.apply_to_draft || step.apply_to_grounding || step.apply_to)}</div>
|
|
|
+ ${hasRelation ? `
|
|
|
+ <td style="padding: 14px 10px; color: var(--text-secondary); font-size: 0.9em; position: relative; border-right: 1px dashed rgba(0,0,0,0.05);">
|
|
|
+ <div class="cell-content" style="max-height: 50px; overflow: hidden; font-family: monospace;">${step.relation ? escapeHtml(step.relation) : '-'}</div>
|
|
|
<div class="cell-fade"></div>
|
|
|
- </td>
|
|
|
- <td style="padding: 14px 10px; color: var(--text-main); font-size: 0.95em; line-height: 1.5; position: relative;">
|
|
|
+ </td>` : ''}
|
|
|
+ <td style="padding: 14px 10px; color: var(--text-main); font-size: 0.95em; line-height: 1.5; position: relative; border-right: 1px solid rgba(0,0,0,0.05);">
|
|
|
<div class="cell-content" style="max-height: 50px; overflow: hidden; white-space: pre-wrap;">${step.body ? escapeHtml(step.body) : '-'}</div>
|
|
|
<div class="cell-fade"></div>
|
|
|
</td>
|
|
|
<td style="padding: 14px 10px; position: relative;">
|
|
|
- <div class="cell-content" style="max-height: 50px; overflow: hidden;">${toolsHtml || '-'}</div>
|
|
|
- <div class="cell-fade"></div>
|
|
|
+ ${fragmentsHtml}
|
|
|
</td>
|
|
|
</tr>
|
|
|
`;
|