1. 优化工作坊列表布局,实现标题与按钮垂直对齐 2. 实现查看结果按钮的防抖功能和加载状态 3. 优化时间排序逻辑,默认按时间降序,点击按钮按时间升序 4. 实现多选功能,勾选框与工作坊名称高度对齐 5. 添加AI结果缓存功能,避免重复API调用 6. 优化空状态显示,居中创建第一个工作坊按钮 7. 完善README.md文档,添加新功能描述和使用指南 8. 调整字体样式,优化视觉效果
420 lines
20 KiB
HTML
420 lines
20 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>多 Agent 决策工作坊</title>
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<!-- 页面标题 -->
|
||
<header>
|
||
<h1>多 Agent 决策工作坊</h1>
|
||
<p>通过多角色辩论生成更全面的决策方案</p>
|
||
</header>
|
||
|
||
<!-- 创建工作坊按钮 -->
|
||
<button onclick="window.location.href='{{ url_for('create_workshop') }}'" class="btn-primary">创建新工作坊</button>
|
||
|
||
<!-- 流程指南 -->
|
||
<div class="card">
|
||
<h3>工作流程指南</h3>
|
||
<div class="process-indicator">
|
||
<div class="process-step">
|
||
<div class="process-step-number">1</div>
|
||
<div class="process-step-text">创建工作坊</div>
|
||
</div>
|
||
<div class="process-arrow">→</div>
|
||
<div class="process-step">
|
||
<div class="process-step-number">2</div>
|
||
<div class="process-step-text">配置角色</div>
|
||
</div>
|
||
<div class="process-arrow">→</div>
|
||
<div class="process-step">
|
||
<div class="process-step-number">3</div>
|
||
<div class="process-step-text">开始辩论</div>
|
||
</div>
|
||
<div class="process-arrow">→</div>
|
||
<div class="process-step">
|
||
<div class="process-step-number">4</div>
|
||
<div class="process-step-text">查看结果</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 工作坊列表 -->
|
||
<div class="workshop-header">
|
||
<h2>工作坊列表</h2>
|
||
<!-- 多选选项和排序按钮 -->
|
||
<div class="header-actions">
|
||
<!-- 多选选项 -->
|
||
<div class="select-options">
|
||
<button id="multiSelectToggle" class="btn-secondary">多选</button>
|
||
</div>
|
||
<!-- 排序按钮 -->
|
||
<div class="sort-buttons">
|
||
<button onclick="sortWorkshops('time')" class="btn-secondary {% if sort_by == 'time' %}active{% endif %}">按时间排序</button>
|
||
<button onclick="sortWorkshops('name')" class="btn-secondary {% if sort_by == 'name' %}active{% endif %}">按名称首字母排序</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 批量操作按钮 -->
|
||
<div class="batch-actions" id="batchActions" style="display: none;">
|
||
<button onclick="batchDeleteWorkshops()" class="btn-danger">批量删除</button>
|
||
<span id="selectedCount">已选择 0 个工作坊</span>
|
||
</div>
|
||
<div class="workshop-list" id="workshopList">
|
||
{% if workshops %}
|
||
{% for workshop_id, workshop in workshops.items() %}
|
||
<div class="workshop-card">
|
||
<!-- 复选框 -->
|
||
<div class="card-checkbox" style="display: none;">
|
||
<input type="checkbox" class="workshop-checkbox" data-id="{{ workshop_id }}">
|
||
</div>
|
||
<h3>{{ workshop.name }}</h3>
|
||
<p>{{ workshop.goal }}</p>
|
||
|
||
<div class="workshop-details">
|
||
<p><strong>角色数量:</strong> {{ workshop.roles|length }}</p>
|
||
<p><strong>辩论内容:</strong> {{ workshop.debate_content|length }} 条观点</p>
|
||
<p><strong>创建时间:</strong> {{ workshop.created_at.strftime('%Y-%m-%d %H:%M') if workshop.created_at else '未知' }}</p>
|
||
{% if workshop.final_decision_time %}
|
||
<p><strong>最终决策时间:</strong> {{ workshop.final_decision_time.strftime('%Y-%m-%d %H:%M') }}</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 最终决策 -->
|
||
<div class="final-decision">
|
||
<p><strong>最终决策:</strong>
|
||
{% if workshop.final_decision %}
|
||
<span class="decision-text">{{ workshop.final_decision }}</span>
|
||
{% else %}
|
||
<span class="decision-pending">决策尚未确定</span>
|
||
{% endif %}
|
||
</p>
|
||
</div>
|
||
|
||
<!-- 工作坊状态 -->
|
||
{% if workshop.roles|length > 0 and workshop.debate_content|length > 0 %}
|
||
<span class="status-badge completed">已完成辩论</span>
|
||
{% elif workshop.roles|length > 0 %}
|
||
<span class="status-badge active">已配置角色</span>
|
||
{% else %}
|
||
<span class="status-badge">已创建</span>
|
||
{% endif %}
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="workshop-actions">
|
||
<button onclick="window.location.href='{{ url_for('configure_roles', workshop_id=workshop_id) }}'" class="btn-secondary">配置角色</button>
|
||
<button onclick="window.location.href='{{ url_for('start_debate', workshop_id=workshop_id) }}'" class="btn-primary">开始辩论</button>
|
||
<button onclick="window.location.href='{{ url_for('show_results', workshop_id=workshop_id) }}'" class="btn-success">查看结果</button>
|
||
<button onclick="deleteWorkshop({{ workshop_id }})" class="btn-danger">删除</button>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="card">
|
||
<div class="empty-state">
|
||
<button onclick="window.location.href='{{ url_for('create_workshop') }}'" class="btn-primary">创建第一个工作坊</button>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 无刷新排序工作坊
|
||
function sortWorkshops(sortBy) {
|
||
// 更新按钮的active状态
|
||
const buttons = document.querySelectorAll('.sort-buttons button');
|
||
buttons.forEach(button => {
|
||
button.classList.remove('active');
|
||
});
|
||
// 为当前点击的按钮添加active状态
|
||
if (sortBy === 'time') {
|
||
buttons[0].classList.add('active');
|
||
} else {
|
||
buttons[1].classList.add('active');
|
||
}
|
||
|
||
// 发送AJAX请求获取排序后的数据
|
||
// 将time转换为time_asc,实现按时间升序排序
|
||
const sortParam = sortBy === 'time' ? 'time_asc' : sortBy;
|
||
fetch(`/api/workshops?sort_by=${sortParam}`)
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
// 更新工作坊列表
|
||
const workshopList = document.getElementById('workshopList');
|
||
workshopList.innerHTML = '';
|
||
|
||
if (data.length > 0) {
|
||
data.forEach(workshop => {
|
||
// 创建工作坊卡片
|
||
const workshopCard = document.createElement('div');
|
||
workshopCard.className = 'workshop-card';
|
||
|
||
// 构建卡片内容
|
||
let cardContent = `
|
||
<!-- 复选框 -->
|
||
<div class="card-checkbox" style="display: none;">
|
||
<input type="checkbox" class="workshop-checkbox" data-id="${workshop.id}">
|
||
</div>
|
||
<h3>${workshop.name}</h3>
|
||
<p>${workshop.goal}</p>
|
||
|
||
<div class="workshop-details">
|
||
<p><strong>角色数量:</strong> ${workshop.roles.length}</p>
|
||
<p><strong>辩论内容:</strong> ${workshop.debate_content.length} 条观点</p>
|
||
<p><strong>创建时间:</strong> ${workshop.created_at || '未知'}</p>
|
||
`;
|
||
|
||
// 添加最终决策时间(如果有)
|
||
if (workshop.final_decision_time) {
|
||
cardContent += `
|
||
<p><strong>最终决策时间:</strong> ${workshop.final_decision_time}</p>
|
||
`;
|
||
}
|
||
|
||
// 添加最终决策
|
||
cardContent += `
|
||
</div>
|
||
|
||
<!-- 最终决策 -->
|
||
<div class="final-decision">
|
||
<p><strong>最终决策:</strong>
|
||
`;
|
||
|
||
// 添加最终决策内容
|
||
if (workshop.final_decision) {
|
||
cardContent += `
|
||
<span class="decision-text">${workshop.final_decision}</span>
|
||
`;
|
||
} else {
|
||
cardContent += `
|
||
<span class="no-decision">暂无</span>
|
||
`;
|
||
}
|
||
|
||
// 添加工作坊状态
|
||
cardContent += `
|
||
</p>
|
||
</div>
|
||
|
||
<!-- 工作坊状态 -->
|
||
`;
|
||
|
||
if (workshop.roles.length > 0 && workshop.debate_content.length > 0) {
|
||
cardContent += `
|
||
<span class="status-badge completed">已完成辩论</span>
|
||
`;
|
||
} else if (workshop.roles.length > 0) {
|
||
cardContent += `
|
||
<span class="status-badge active">已配置角色</span>
|
||
`;
|
||
} else {
|
||
cardContent += `
|
||
<span class="status-badge">已创建</span>
|
||
`;
|
||
}
|
||
|
||
// 添加操作按钮
|
||
cardContent += `
|
||
<!-- 操作按钮 -->
|
||
<div class="workshop-actions">
|
||
<button onclick="window.location.href='/workshop/${workshop.id}/configure_roles'" class="btn-secondary">配置角色</button>
|
||
<button onclick="window.location.href='/workshop/${workshop.id}/debate'" class="btn-primary">开始辩论</button>
|
||
<button onclick="window.location.href='/workshop/${workshop.id}/results'" class="btn-success">查看结果</button>
|
||
<button onclick="deleteWorkshop(${workshop.id})" class="btn-danger">删除</button>
|
||
</div>
|
||
`;
|
||
|
||
workshopCard.innerHTML = cardContent;
|
||
workshopList.appendChild(workshopCard);
|
||
});
|
||
} else {
|
||
// 显示空状态
|
||
workshopList.innerHTML = `
|
||
<div class="card">
|
||
<div class="empty-state">
|
||
<p>暂无工作坊,请创建新工作坊</p>
|
||
<button onclick="window.location.href='/create'" class="btn-primary">创建第一个工作坊</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('获取数据失败:', error);
|
||
});
|
||
}
|
||
|
||
// 删除单个工作坊
|
||
function deleteWorkshop(id) {
|
||
if (confirm('确定要删除这个工作坊吗?')) {
|
||
fetch(`/api/workshop/${id}`, {
|
||
method: 'DELETE'
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// 重新获取工作坊列表
|
||
sortWorkshops(document.querySelector('.sort-buttons button.active') ? 'name' : 'time');
|
||
} else {
|
||
alert('删除失败: ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('删除失败:', error);
|
||
alert('删除失败,请稍后重试');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 批量删除工作坊
|
||
function batchDeleteWorkshops() {
|
||
const selectedIds = getSelectedWorkshopIds();
|
||
if (selectedIds.length === 0) {
|
||
alert('请先选择要删除的工作坊');
|
||
return;
|
||
}
|
||
|
||
if (confirm(`确定要删除选中的 ${selectedIds.length} 个工作坊吗?`)) {
|
||
fetch('/api/workshops/batch_delete', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ workshop_ids: selectedIds })
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// 重新获取工作坊列表
|
||
sortWorkshops(document.querySelector('.sort-buttons button.active') ? 'name' : 'time');
|
||
} else {
|
||
alert('批量删除失败: ' + data.message);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('批量删除失败:', error);
|
||
alert('批量删除失败,请稍后重试');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 获取选中的工作坊ID
|
||
function getSelectedWorkshopIds() {
|
||
const checkboxes = document.querySelectorAll('.workshop-checkbox:checked');
|
||
return Array.from(checkboxes).map(checkbox => parseInt(checkbox.dataset.id));
|
||
}
|
||
|
||
// 更新选中状态
|
||
function updateSelectionStatus() {
|
||
const selectedIds = getSelectedWorkshopIds();
|
||
const selectedCount = selectedIds.length;
|
||
const batchActions = document.getElementById('batchActions');
|
||
const selectedCountElement = document.getElementById('selectedCount');
|
||
|
||
if (selectedCount > 0) {
|
||
batchActions.style.display = 'flex';
|
||
selectedCountElement.textContent = `已选择 ${selectedCount} 个工作坊`;
|
||
} else {
|
||
batchActions.style.display = 'none';
|
||
}
|
||
|
||
// 更新全选复选框状态
|
||
const allCheckboxes = document.querySelectorAll('.workshop-checkbox');
|
||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||
if (selectAllCheckbox) {
|
||
if (allCheckboxes.length > 0) {
|
||
selectAllCheckbox.checked = allCheckboxes.length === selectedIds.length;
|
||
} else {
|
||
selectAllCheckbox.checked = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 切换多选模式
|
||
let isMultiSelectMode = false;
|
||
function toggleMultiSelectMode() {
|
||
const checkboxes = document.querySelectorAll('.card-checkbox');
|
||
|
||
// 检查是否有工作坊(即是否有复选框)
|
||
if (checkboxes.length === 0) {
|
||
return; // 没有工作坊时,不执行任何操作
|
||
}
|
||
|
||
isMultiSelectMode = !isMultiSelectMode;
|
||
const multiSelectToggle = document.getElementById('multiSelectToggle');
|
||
const batchActions = document.getElementById('batchActions');
|
||
|
||
if (isMultiSelectMode) {
|
||
// 进入多选模式
|
||
checkboxes.forEach(checkbox => {
|
||
checkbox.style.display = 'block';
|
||
});
|
||
multiSelectToggle.textContent = '取消多选';
|
||
multiSelectToggle.classList.remove('btn-secondary');
|
||
multiSelectToggle.classList.add('btn-primary');
|
||
// 显示批量操作按钮
|
||
batchActions.style.display = 'flex';
|
||
// 重置选中状态
|
||
document.querySelectorAll('.workshop-checkbox').forEach(checkbox => {
|
||
checkbox.checked = false;
|
||
});
|
||
updateSelectionStatus();
|
||
} else {
|
||
// 退出多选模式
|
||
checkboxes.forEach(checkbox => {
|
||
checkbox.style.display = 'none';
|
||
});
|
||
multiSelectToggle.textContent = '多选';
|
||
multiSelectToggle.classList.remove('btn-primary');
|
||
multiSelectToggle.classList.add('btn-secondary');
|
||
// 隐藏批量操作按钮
|
||
batchActions.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 全选/取消全选
|
||
function toggleSelectAll() {
|
||
const checkboxes = document.querySelectorAll('.workshop-checkbox');
|
||
const firstCheckbox = checkboxes[0];
|
||
if (firstCheckbox) {
|
||
const shouldCheck = !firstCheckbox.checked;
|
||
checkboxes.forEach(checkbox => {
|
||
checkbox.checked = shouldCheck;
|
||
});
|
||
updateSelectionStatus();
|
||
}
|
||
}
|
||
|
||
// 监听工作坊复选框变化
|
||
document.addEventListener('change', function(event) {
|
||
if (event.target.classList.contains('workshop-checkbox')) {
|
||
updateSelectionStatus();
|
||
}
|
||
});
|
||
|
||
// 初始化时绑定事件
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 绑定排序按钮事件
|
||
const sortButtons = document.querySelectorAll('.sort-buttons button');
|
||
sortButtons.forEach(button => {
|
||
button.addEventListener('click', function() {
|
||
const sortBy = this.textContent.includes('时间') ? 'time' : 'name';
|
||
sortWorkshops(sortBy);
|
||
});
|
||
});
|
||
|
||
// 绑定多选模式切换按钮事件
|
||
const multiSelectToggle = document.getElementById('multiSelectToggle');
|
||
multiSelectToggle.addEventListener('click', toggleMultiSelectMode);
|
||
|
||
// 初始更新选中状态
|
||
updateSelectionStatus();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |