feat: 新增参考材料前端 UI
- 在左侧片段选择卡片下方添加参考材料卡片
- 支持动态添加/删除参考材料
- 每个材料包含可选标题和内容输入
- 标题为空时自动显示为「参考材料{n}」
- 材料内容实时预览到 Prompt
- 草稿自动保存/加载包含材料数据
- 保存会话时支持保存材料
This commit is contained in:
+82
-1
@@ -73,6 +73,17 @@
|
|||||||
<div class="card-body" id="snippets-container"></div>
|
<div class="card-body" id="snippets-container"></div>
|
||||||
</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 -->
|
<!-- CLAUDE.md -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="collapse-header" onclick="toggleCollapse(this)">
|
<div class="collapse-header" onclick="toggleCollapse(this)">
|
||||||
@@ -173,6 +184,7 @@
|
|||||||
let allSnippets = [];
|
let allSnippets = [];
|
||||||
let selectedTags = {}; // tagId -> optionId
|
let selectedTags = {}; // tagId -> optionId
|
||||||
let selectedSnippets = []; // [{snippetId, sortOrder}]
|
let selectedSnippets = []; // [{snippetId, sortOrder}]
|
||||||
|
let materials = []; // [{title, content}]
|
||||||
let suggestions = [];
|
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
|
// Suggestions
|
||||||
const adoptedSuggestions = suggestions.filter(s => s.adopted);
|
const adoptedSuggestions = suggestions.filter(s => s.adopted);
|
||||||
if (adoptedSuggestions.length > 0) {
|
if (adoptedSuggestions.length > 0) {
|
||||||
@@ -441,6 +463,51 @@
|
|||||||
document.getElementById('tag-search')?.addEventListener('input', debounce(() => renderTags(), 300));
|
document.getElementById('tag-search')?.addEventListener('input', debounce(() => renderTags(), 300));
|
||||||
document.getElementById('snippet-search')?.addEventListener('input', debounce(() => renderSnippets(), 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('custom-content')?.addEventListener('input', debounce(() => updatePreview(), 200));
|
||||||
document.getElementById('project-name')?.addEventListener('input', debounce(() => updatePreview(), 200));
|
document.getElementById('project-name')?.addEventListener('input', debounce(() => updatePreview(), 200));
|
||||||
@@ -591,6 +658,14 @@
|
|||||||
sort_order: s.sortOrder
|
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 {
|
try {
|
||||||
await API.post('/api/builder/sessions', {
|
await API.post('/api/builder/sessions', {
|
||||||
title,
|
title,
|
||||||
@@ -598,7 +673,8 @@
|
|||||||
final_prompt: finalPrompt,
|
final_prompt: finalPrompt,
|
||||||
claude_session_id: claudeSessionId,
|
claude_session_id: claudeSessionId,
|
||||||
tag_option_ids: tagOptionIds,
|
tag_option_ids: tagOptionIds,
|
||||||
snippet_ids: snippetIds
|
snippet_ids: snippetIds,
|
||||||
|
materials: materialsData
|
||||||
});
|
});
|
||||||
closeSaveModal();
|
closeSaveModal();
|
||||||
Toast.success('会话已保存');
|
Toast.success('会话已保存');
|
||||||
@@ -616,6 +692,7 @@
|
|||||||
claudeMD: document.getElementById('claude-md').value,
|
claudeMD: document.getElementById('claude-md').value,
|
||||||
selectedTags: { ...selectedTags },
|
selectedTags: { ...selectedTags },
|
||||||
selectedSnippets: [...selectedSnippets],
|
selectedSnippets: [...selectedSnippets],
|
||||||
|
materials: materials.map(m => ({ title: m.title, content: m.content })),
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
@@ -633,6 +710,8 @@
|
|||||||
document.getElementById('claude-md').value = draft.claudeMD || '';
|
document.getElementById('claude-md').value = draft.claudeMD || '';
|
||||||
selectedTags = draft.selectedTags || {};
|
selectedTags = draft.selectedTags || {};
|
||||||
selectedSnippets = draft.selectedSnippets || [];
|
selectedSnippets = draft.selectedSnippets || [];
|
||||||
|
materials = draft.materials || [];
|
||||||
|
renderMaterials();
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetBuilder() {
|
function resetBuilder() {
|
||||||
@@ -644,11 +723,13 @@
|
|||||||
document.getElementById('session-prompts').classList.add('hidden');
|
document.getElementById('session-prompts').classList.add('hidden');
|
||||||
selectedTags = {};
|
selectedTags = {};
|
||||||
selectedSnippets = [];
|
selectedSnippets = [];
|
||||||
|
materials = [];
|
||||||
suggestions = [];
|
suggestions = [];
|
||||||
document.getElementById('suggestions-card').style.display = 'none';
|
document.getElementById('suggestions-card').style.display = 'none';
|
||||||
Draft.clear('builder');
|
Draft.clear('builder');
|
||||||
renderTags();
|
renderTags();
|
||||||
renderSnippets();
|
renderSnippets();
|
||||||
|
renderMaterials();
|
||||||
updatePreview();
|
updatePreview();
|
||||||
Toast.success('已重置');
|
Toast.success('已重置');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user