sunpayus/templates/index.html
st2411020120 d1066566c0 完善项目功能和用户体验:
1. 优化工作坊列表布局,实现标题与按钮垂直对齐
2. 实现查看结果按钮的防抖功能和加载状态
3. 优化时间排序逻辑,默认按时间降序,点击按钮按时间升序
4. 实现多选功能,勾选框与工作坊名称高度对齐
5. 添加AI结果缓存功能,避免重复API调用
6. 优化空状态显示,居中创建第一个工作坊按钮
7. 完善README.md文档,添加新功能描述和使用指南
8. 调整字体样式,优化视觉效果
2026-01-09 04:04:55 +08:00

420 lines
20 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>