scratchpad.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. function renderStructuredData(items, type) {
  2. if (!items || items.length === 0) {
  3. return `<div style="color:var(--text-muted); padding: 1rem;">暂无${type === 'workflow' ? '工序' : '能力'}数据</div>`;
  4. }
  5. let html = '';
  6. items.forEach((item, idx) => {
  7. let title = item.method || item.name || (item.action && item.action.description) || (type === 'workflow' ? '工作流' : `节点 ${idx + 1}`);
  8. // Unstructured what tags
  9. let unstructTags = '';
  10. if (item.unstructured_what && Array.isArray(item.unstructured_what)) {
  11. unstructTags = item.unstructured_what.map(t => `<span class="unstruct-badge">${t}</span>`).join('');
  12. }
  13. html += `<div class="structured-card">
  14. <div class="structured-card-title-row" style="display:flex; align-items:center; gap: 8px; margin-bottom: 1rem;">
  15. <div class="structured-card-title" style="margin:0;">${title}</div>
  16. ${unstructTags}
  17. </div>
  18. `;
  19. // Render apply_to or apply_to_draft
  20. const applyTo = item.apply_to_draft || item.apply_to;
  21. const suggestApplyTo = item.apply_to_draft ? null : item.suggest_apply_to;
  22. if (applyTo && typeof applyTo === 'object' && Object.keys(applyTo).length > 0) {
  23. html += `<div class="structured-row">
  24. <div class="structured-label">apply_to</div>
  25. <div class="structured-value" style="display:flex; flex-direction:column; gap:6px;">`;
  26. const renderPathBadge = (path, highlight = false) => {
  27. const pathStr = typeof path === 'object' && path !== null
  28. ? (path.element || path.category_path || path.path || '')
  29. : String(path || '');
  30. if (!pathStr) return '';
  31. const parts = pathStr.split('/');
  32. const leaf = parts.pop();
  33. const prefix = parts.length > 0 ? parts.join('/') + '/' : '';
  34. const leafStyle = highlight ? 'background:#eff6ff; color:#2563eb; border:1px solid #bfdbfe;' : '';
  35. const evidence = typeof path === 'object' && path !== null && ('body_excerpt' in path || 'body_excerpt_note' in path)
  36. ? `<div style="display:flex; flex-direction:column; gap:2px; font-size:0.78rem; color:#64748b; margin-top:4px;">
  37. <div style="display:flex; flex-direction:column; gap:2px;"><strong style="color:#94a3b8;">关联做法</strong><span>${path.body_excerpt_note ? String(path.body_excerpt_note).replace(/</g, '&lt;').replace(/>/g, '&gt;') : '<span style="color:#cbd5e1;font-style:italic;">空</span>'}</span></div>
  38. </div>`
  39. : '';
  40. return `<span style="display:inline-flex; flex-direction:column; gap:4px;">
  41. <span class="apply-to-path-item" ${highlight ? 'style="border: 2px dashed #94a3b8; background: transparent;"' : ''}>
  42. ${prefix ? `<span class="apply-to-path-prefix">${prefix}</span>` : ''}
  43. <span class="apply-to-path-leaf" style="${leafStyle}">${leaf}</span>
  44. </span>
  45. ${evidence}
  46. </span>`;
  47. };
  48. Object.entries(applyTo).forEach(([k, v]) => {
  49. if (Array.isArray(v) && v.length > 0) {
  50. html += `<div class="apply-to-subrow">
  51. <span class="apply-to-key-badge">${k}</span>
  52. <div class="apply-to-values">`;
  53. v.forEach(path => {
  54. html += renderPathBadge(path);
  55. });
  56. html += `</div></div>`;
  57. }
  58. });
  59. const suggestItems = Array.isArray(suggestApplyTo)
  60. ? suggestApplyTo
  61. : (typeof suggestApplyTo === 'string' && suggestApplyTo.trim() ? [{ path: suggestApplyTo }] : []);
  62. if (suggestItems.length > 0) {
  63. html += `<div class="apply-to-subrow">
  64. <span class="apply-to-key-badge">建议</span>
  65. <div class="apply-to-values">${suggestItems.map(item => renderPathBadge(item.path || item, true)).join('')}</div>
  66. </div>`;
  67. }
  68. html += `</div></div>`;
  69. }
  70. if (item.action && typeof item.action === 'object' && (item.action.description || item.action.reasoning)) {
  71. const actionDescription = item.action.description ? String(item.action.description).replace(/</g, '&lt;').replace(/>/g, '&gt;') : '';
  72. const actionReasoning = item.action.reasoning ? String(item.action.reasoning).replace(/</g, '&lt;').replace(/>/g, '&gt;') : '';
  73. html += `<div class="structured-row">
  74. <div class="structured-label">action</div>
  75. <div class="structured-value">
  76. <style>
  77. .action-description-tooltip:hover .action-reasoning-popover { display:block !important; }
  78. </style>
  79. ${actionDescription ? `<span class="action-description-tooltip" style="position:relative; display:inline-block;">
  80. <span class="data-type-badge">${actionDescription}</span>
  81. ${actionReasoning ? `<span class="action-reasoning-popover" style="display:none; position:absolute; left:0; top:calc(100% + 6px); z-index:50; width:300px; max-width:60vw; padding:8px 10px; border-radius:8px; background:#0f172a; color:#f8fafc; box-shadow:0 10px 25px rgba(15,23,42,0.18); font-size:0.86em; line-height:1.5; white-space:normal; font-weight:400;">${actionReasoning}</span>` : ''}
  82. </span>` : ''}
  83. </div>
  84. </div>`;
  85. }
  86. // Render stage
  87. if (item.stage) {
  88. let stages = Array.isArray(item.stage) ? item.stage : [item.stage];
  89. if (stages.length > 0) {
  90. html += `<div class="structured-row">
  91. <div class="structured-label">stage</div>
  92. <div class="structured-value">`;
  93. stages.forEach(st => {
  94. const stageUpper = st.toUpperCase();
  95. const stageClass = stageUpper === 'GENERATE' ? 'stage-blue' : 'stage-purple';
  96. html += `<span class="structured-badge ${stageClass}">${stageUpper}</span>`;
  97. });
  98. html += `</div></div>`;
  99. }
  100. }
  101. // Render effects
  102. if (item.effects && Array.isArray(item.effects) && item.effects.length > 0) {
  103. html += `<div class="structured-row">
  104. <div class="structured-label">effects</div>
  105. <div class="structured-value">
  106. <ul class="effects-list">
  107. ${item.effects.map(li => `<li>${li.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</li>`).join('')}
  108. </ul>
  109. </div>
  110. </div>`;
  111. }
  112. // Render body
  113. if (item.body && typeof item.body === 'string') {
  114. html += `<div class="structured-row">
  115. <div class="structured-label">body</div>
  116. <div class="structured-value">${item.body.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</div>
  117. </div>`;
  118. }
  119. // Render steps array specially
  120. if (item.steps && Array.isArray(item.steps)) {
  121. html += `<div class="structured-row">
  122. <div class="structured-label">steps</div>
  123. <div class="structured-value">
  124. <div class="steps-container">`;
  125. item.steps.forEach((step, stepIdx) => {
  126. const stepTitle = step.method || step.description || `步骤 ${step.order || stepIdx + 1}`;
  127. html += `
  128. <div class="step-item">
  129. <div class="step-header">
  130. <span class="step-number">${step.order || stepIdx + 1}</span>
  131. <span class="step-title">${stepTitle.replace(/</g, '&lt;').replace(/>/g, '&gt;')}</span>
  132. </div>
  133. <div class="step-body">
  134. ${step.body ? step.body.replace(/</g, '&lt;').replace(/>/g, '&gt;') : ''}
  135. </div>
  136. ${step.tools && step.tools.length > 0 ? `<div class="step-tools">
  137. ${step.tools.map(t => `<span class="structured-badge tool-badge">${t}</span>`).join('')}
  138. </div>` : ''}
  139. </div>
  140. `;
  141. });
  142. html += `</div></div></div>`;
  143. }
  144. // Helper for inputs/outputs
  145. const renderDataObjList = (list) => {
  146. return list.map(io => {
  147. const dt = io.data_type || '未知';
  148. const desc = io.description || '';
  149. return `<div class="io-item"><span class="data-type-badge">${dt}</span><span class="io-desc">${desc}</span></div>`;
  150. }).join('');
  151. };
  152. // Render inputs
  153. if (item.inputs && Array.isArray(item.inputs) && item.inputs.length > 0) {
  154. html += `<div class="structured-row">
  155. <div class="structured-label">inputs</div>
  156. <div class="structured-value" style="display:flex; flex-direction:column; gap:4px;">
  157. ${renderDataObjList(item.inputs)}
  158. </div>
  159. </div>`;
  160. } else if (item.inputs && typeof item.inputs === 'object' && Object.keys(item.inputs).length > 0 && !Array.isArray(item.inputs)) {
  161. // Fallback for old schema
  162. html += `<div class="structured-row"><div class="structured-label">inputs</div><div class="structured-value">`;
  163. Object.entries(item.inputs).forEach(([k, v]) => {
  164. html += `<div class="io-item"><span class="data-type-badge">${k}</span><span class="io-desc">${v}</span></div>`;
  165. });
  166. html += `</div></div>`;
  167. }
  168. // Render outputs
  169. if (item.outputs && Array.isArray(item.outputs) && item.outputs.length > 0) {
  170. html += `<div class="structured-row">
  171. <div class="structured-label">outputs</div>
  172. <div class="structured-value" style="display:flex; flex-direction:column; gap:4px;">
  173. ${renderDataObjList(item.outputs)}
  174. </div>
  175. </div>`;
  176. } else if (item.outputs && typeof item.outputs === 'object' && Object.keys(item.outputs).length > 0 && !Array.isArray(item.outputs)) {
  177. // Fallback for old schema
  178. html += `<div class="structured-row"><div class="structured-label">outputs</div><div class="structured-value">`;
  179. Object.entries(item.outputs).forEach(([k, v]) => {
  180. html += `<div class="io-item"><span class="data-type-badge">${k}</span><span class="io-desc">${v}</span></div>`;
  181. });
  182. html += `</div></div>`;
  183. }
  184. // Render tools
  185. if (item.tools && Array.isArray(item.tools) && item.tools.length > 0) {
  186. html += `<div class="structured-row">
  187. <div class="structured-label">tools</div>
  188. <div class="structured-value">
  189. ${item.tools.map(t => `<span class="structured-badge tool-badge">${t}</span>`).join('')}
  190. </div>
  191. </div>`;
  192. }
  193. html += `</div>`;
  194. });
  195. return html;
  196. }