feat: 新增参考材料前端 UI
- 在左侧片段选择卡片下方添加参考材料卡片
- 支持动态添加/删除参考材料
- 每个材料包含可选标题和内容输入
- 标题为空时自动显示为「参考材料{n}」
- 材料内容实时预览到 Prompt
- 草稿自动保存/加载包含材料数据
- 保存会话时支持保存材料
This commit is contained in:
+82
-1
@@ -73,6 +73,17 @@
|
||||
<div class="card-body" id="snippets-container"></div>
|
||||
</div>
|
||||
|
||||
<!-- 参考材料 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
📦 参考材料
|
||||
<button class="btn btn-sm" onclick="addMaterial()">+ 添加</button>
|
||||
</div>
|
||||
<div class="card-body" id="materials-container">
|
||||
<div class="text-sm text-muted" id="materials-empty">暂无参考材料</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CLAUDE.md -->
|
||||
<div class="card">
|
||||
<div class="collapse-header" onclick="toggleCollapse(this)">
|
||||
@@ -173,6 +184,7 @@
|
||||
let allSnippets = [];
|
||||
let selectedTags = {}; // tagId -> optionId
|
||||
let selectedSnippets = []; // [{snippetId, sortOrder}]
|
||||
let materials = []; // [{title, content}]
|
||||
let suggestions = [];
|
||||
|
||||
// 初始化
|
||||
@@ -380,6 +392,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Materials
|
||||
const validMaterials = materials.filter(m => m.content.trim());
|
||||
if (validMaterials.length > 0) {
|
||||
const materialParts = validMaterials.map((m, i) => {
|
||||
const title = m.title.trim() || `参考材料${i + 1}`;
|
||||
return `--- ${title} ---\n${m.content}`;
|
||||
});
|
||||
parts.push(`## 参考材料\n\n${materialParts.join('\n\n')}`);
|
||||
}
|
||||
|
||||
// Suggestions
|
||||
const adoptedSuggestions = suggestions.filter(s => s.adopted);
|
||||
if (adoptedSuggestions.length > 0) {
|
||||
@@ -441,6 +463,51 @@
|
||||
document.getElementById('tag-search')?.addEventListener('input', debounce(() => renderTags(), 300));
|
||||
document.getElementById('snippet-search')?.addEventListener('input', debounce(() => renderSnippets(), 300));
|
||||
|
||||
// 参考材料管理
|
||||
function addMaterial() {
|
||||
materials.push({ title: '', content: '' });
|
||||
renderMaterials();
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
function removeMaterial(index) {
|
||||
materials.splice(index, 1);
|
||||
renderMaterials();
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
function renderMaterials() {
|
||||
const container = document.getElementById('materials-container');
|
||||
const emptyHint = document.getElementById('materials-empty');
|
||||
|
||||
if (materials.length === 0) {
|
||||
container.innerHTML = '<div class="text-sm text-muted" id="materials-empty">暂无参考材料</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = materials.map((m, i) => `
|
||||
<div class="mb-3 p-3" style="background:var(--bg-tertiary);border-radius:8px">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<input class="input" placeholder="参考材料${i + 1}" value="${escapeHtml(m.title)}"
|
||||
oninput="updateMaterialTitle(${i}, this.value)" style="flex:1;margin-right:8px">
|
||||
<button class="btn btn-sm" onclick="removeMaterial(${i})" style="color:var(--error-color)">✕</button>
|
||||
</div>
|
||||
<textarea class="textarea" placeholder="输入参考材料内容..." rows="3"
|
||||
oninput="updateMaterialContent(${i}, this.value)">${escapeHtml(m.content)}</textarea>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function updateMaterialTitle(index, title) {
|
||||
materials[index].title = title;
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
function updateMaterialContent(index, content) {
|
||||
materials[index].content = content;
|
||||
updatePreview();
|
||||
}
|
||||
|
||||
// 自定义输入变化时更新预览
|
||||
document.getElementById('custom-content')?.addEventListener('input', debounce(() => updatePreview(), 200));
|
||||
document.getElementById('project-name')?.addEventListener('input', debounce(() => updatePreview(), 200));
|
||||
@@ -591,6 +658,14 @@
|
||||
sort_order: s.sortOrder
|
||||
}));
|
||||
|
||||
const materialsData = materials
|
||||
.filter(m => m.content.trim())
|
||||
.map((m, i) => ({
|
||||
title: m.title.trim(),
|
||||
content: m.content,
|
||||
sort_order: i
|
||||
}));
|
||||
|
||||
try {
|
||||
await API.post('/api/builder/sessions', {
|
||||
title,
|
||||
@@ -598,7 +673,8 @@
|
||||
final_prompt: finalPrompt,
|
||||
claude_session_id: claudeSessionId,
|
||||
tag_option_ids: tagOptionIds,
|
||||
snippet_ids: snippetIds
|
||||
snippet_ids: snippetIds,
|
||||
materials: materialsData
|
||||
});
|
||||
closeSaveModal();
|
||||
Toast.success('会话已保存');
|
||||
@@ -616,6 +692,7 @@
|
||||
claudeMD: document.getElementById('claude-md').value,
|
||||
selectedTags: { ...selectedTags },
|
||||
selectedSnippets: [...selectedSnippets],
|
||||
materials: materials.map(m => ({ title: m.title, content: m.content })),
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
@@ -633,6 +710,8 @@
|
||||
document.getElementById('claude-md').value = draft.claudeMD || '';
|
||||
selectedTags = draft.selectedTags || {};
|
||||
selectedSnippets = draft.selectedSnippets || [];
|
||||
materials = draft.materials || [];
|
||||
renderMaterials();
|
||||
}
|
||||
|
||||
function resetBuilder() {
|
||||
@@ -644,11 +723,13 @@
|
||||
document.getElementById('session-prompts').classList.add('hidden');
|
||||
selectedTags = {};
|
||||
selectedSnippets = [];
|
||||
materials = [];
|
||||
suggestions = [];
|
||||
document.getElementById('suggestions-card').style.display = 'none';
|
||||
Draft.clear('builder');
|
||||
renderTags();
|
||||
renderSnippets();
|
||||
renderMaterials();
|
||||
updatePreview();
|
||||
Toast.success('已重置');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user