830 lines
30 KiB
HTML
830 lines
30 KiB
HTML
{% extends 'ewoooc_base.html' %}
|
|
{% block title %}AI 生成歷史 · EwoooC{% endblock %}
|
|
|
|
{% block ewooo_content %}
|
|
<div class="ai-history-page">
|
|
<!-- 頁面標題 -->
|
|
<section class="ai-history-hero">
|
|
<div>
|
|
<h1 class="ai-history-title">
|
|
<i class="fas fa-history"></i>
|
|
AI 生成歷史
|
|
</h1>
|
|
<p class="ai-history-subtitle">回收有效文案,追蹤哪些銷售動作可再用。</p>
|
|
</div>
|
|
<div class="ai-history-actions">
|
|
<a href="/ai_recommend" class="btn btn-primary ai-history-action-btn">
|
|
<i class="fas fa-magic me-1"></i>生成新文案
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- 統計卡片 -->
|
|
<div class="row mb-4" id="statsRow">
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1 small">總生成次數</p>
|
|
<h3 class="mb-0" id="statTotal">-</h3>
|
|
</div>
|
|
<div class="bg-primary bg-opacity-10 rounded-circle p-3">
|
|
<i class="fas fa-robot text-primary fa-lg"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1 small">收藏文案</p>
|
|
<h3 class="mb-0" id="statFavorite">-</h3>
|
|
</div>
|
|
<div class="bg-warning bg-opacity-10 rounded-circle p-3">
|
|
<i class="fas fa-star text-warning fa-lg"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1 small">已使用文案</p>
|
|
<h3 class="mb-0" id="statUsed">-</h3>
|
|
</div>
|
|
<div class="bg-success bg-opacity-10 rounded-circle p-3">
|
|
<i class="fas fa-check-circle text-success fa-lg"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3 col-sm-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="text-muted mb-1 small">平均生成時間</p>
|
|
<h3 class="mb-0"><span id="statDuration">-</span><small class="text-muted fs-6">秒</small></h3>
|
|
</div>
|
|
<div class="bg-info bg-opacity-10 rounded-circle p-3">
|
|
<i class="fas fa-clock text-info fa-lg"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 篩選列 -->
|
|
<div class="card shadow-sm mb-4 ai-history-panel">
|
|
<div class="card-body py-3">
|
|
<div class="row g-3 align-items-end">
|
|
<div class="col-md-3">
|
|
<label class="form-label small text-muted">搜尋</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-white"><i class="fas fa-search text-muted"></i></span>
|
|
<input type="text" class="form-control" id="searchInput" placeholder="商品名稱或內容...">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label small text-muted">類型</label>
|
|
<select class="form-select" id="filterType">
|
|
<option value="">全部類型</option>
|
|
<option value="copy">銷售文案</option>
|
|
<option value="recommend">商品推薦</option>
|
|
<option value="weather_analysis">天氣分析</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label small text-muted">收藏</label>
|
|
<select class="form-select" id="filterFavorite">
|
|
<option value="">全部</option>
|
|
<option value="true">僅收藏</option>
|
|
<option value="false">未收藏</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label small text-muted">開始日期</label>
|
|
<input type="date" class="form-control" id="filterStartDate">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label small text-muted">結束日期</label>
|
|
<input type="date" class="form-control" id="filterEndDate">
|
|
</div>
|
|
<div class="col-md-1">
|
|
<button class="btn btn-outline-secondary w-100" onclick="resetFilters()">
|
|
<i class="fas fa-undo"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 批次操作列 -->
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<div class="form-check">
|
|
<input type="checkbox" class="form-check-input" id="selectAll">
|
|
<label class="form-check-label" for="selectAll">全選</label>
|
|
</div>
|
|
<button class="btn btn-outline-danger btn-sm" id="batchDeleteBtn" disabled onclick="batchDelete()">
|
|
<i class="fas fa-trash me-1"></i>批次刪除 (<span id="selectedCount">0</span>)
|
|
</button>
|
|
</div>
|
|
<div class="text-muted small">
|
|
共 <strong id="totalCount">0</strong> 筆記錄
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 歷史記錄列表 -->
|
|
<div id="historyList">
|
|
<!-- 動態載入 -->
|
|
</div>
|
|
|
|
<!-- 分頁 -->
|
|
<nav aria-label="Page navigation" class="mt-4">
|
|
<ul class="pagination justify-content-center" id="pagination">
|
|
<!-- 動態生成 -->
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- 編輯 Modal -->
|
|
<div class="modal fade" id="editModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">
|
|
<i class="fas fa-edit me-2"></i>編輯文案
|
|
</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="hidden" id="editId">
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-bold">商品名稱</label>
|
|
<input type="text" class="form-control" id="editProductName" readonly>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-bold">生成內容</label>
|
|
<textarea class="form-control" id="editContent" rows="6"></textarea>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">評分</label>
|
|
<div class="rating-stars" id="editRating">
|
|
<i class="fas fa-star" data-rating="1"></i>
|
|
<i class="fas fa-star" data-rating="2"></i>
|
|
<i class="fas fa-star" data-rating="3"></i>
|
|
<i class="fas fa-star" data-rating="4"></i>
|
|
<i class="fas fa-star" data-rating="5"></i>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">收藏</label>
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="editFavorite">
|
|
<label class="form-check-label" for="editFavorite">標記為收藏</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label fw-bold">已使用</label>
|
|
<div class="form-check form-switch">
|
|
<input class="form-check-input" type="checkbox" id="editUsed">
|
|
<label class="form-check-label" for="editUsed">標記為已使用</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<label class="form-label fw-bold">備註</label>
|
|
<textarea class="form-control" id="editNotes" rows="2" placeholder="添加備註..."></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
|
<button type="button" class="btn btn-primary" onclick="saveEdit()">
|
|
<i class="fas fa-save me-1"></i>儲存變更
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 複製成功 Toast -->
|
|
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
|
<div id="copyToast" class="toast" role="alert">
|
|
<div class="toast-header bg-success text-white">
|
|
<i class="fas fa-check-circle me-2"></i>
|
|
<strong class="me-auto">複製成功</strong>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
<div class="toast-body">
|
|
文案已複製到剪貼簿
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.ai-history-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
}
|
|
|
|
.ai-history-hero {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
gap: 16px;
|
|
align-items: center;
|
|
padding: 22px;
|
|
border: 1px solid var(--momo-border-strong);
|
|
border-radius: 8px;
|
|
background:
|
|
radial-gradient(circle at 18px 18px, rgba(42, 37, 32, 0.12) 1px, transparent 1px),
|
|
linear-gradient(135deg, rgba(242, 178, 90, 0.22), rgba(255, 255, 255, 0.94) 46%, rgba(42, 37, 32, 0.06));
|
|
background-size: 18px 18px, auto;
|
|
box-shadow: var(--momo-shadow-soft);
|
|
}
|
|
|
|
.ai-history-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin: 0;
|
|
color: var(--momo-text-strong);
|
|
font-family: var(--momo-font-display);
|
|
font-size: clamp(1.45rem, 2vw, 2.05rem);
|
|
font-weight: 800;
|
|
letter-spacing: 0;
|
|
}
|
|
|
|
.ai-history-title i {
|
|
color: var(--momo-warm-caramel);
|
|
}
|
|
|
|
.ai-history-subtitle {
|
|
margin: 8px 0 0;
|
|
color: var(--momo-text-muted);
|
|
}
|
|
|
|
.ai-history-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.ai-history-action-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 40px;
|
|
border-radius: 8px;
|
|
background: var(--momo-text-strong);
|
|
border-color: var(--momo-text-strong);
|
|
font-weight: 800;
|
|
}
|
|
|
|
.ai-history-page #statsRow .card,
|
|
.ai-history-panel,
|
|
.history-card {
|
|
border: 1px solid var(--momo-border-subtle) !important;
|
|
border-radius: 8px;
|
|
background: rgba(255, 255, 255, 0.84);
|
|
box-shadow: var(--momo-shadow-soft);
|
|
}
|
|
|
|
.ai-history-page #statsRow .card-body {
|
|
min-height: 112px;
|
|
}
|
|
|
|
.ai-history-page #statsRow h3 {
|
|
color: var(--momo-text-strong);
|
|
font-family: var(--momo-font-mono);
|
|
font-weight: 800;
|
|
}
|
|
|
|
.ai-history-page #statsRow .small,
|
|
.ai-history-page .form-label,
|
|
.ai-history-page .text-muted {
|
|
color: var(--momo-text-muted) !important;
|
|
}
|
|
|
|
.ai-history-panel .input-group-text,
|
|
.ai-history-panel .form-control,
|
|
.ai-history-panel .form-select {
|
|
border-color: var(--momo-border-subtle);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.ai-history-panel .input-group-text {
|
|
background: rgba(250, 247, 240, 0.74) !important;
|
|
}
|
|
|
|
.history-card {
|
|
transition: all 0.2s ease;
|
|
}
|
|
.history-card:hover {
|
|
box-shadow: 0 10px 26px rgba(42, 37, 32, 0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
.history-card.selected {
|
|
border-color: rgba(172, 92, 58, 0.42) !important;
|
|
background: rgba(255, 247, 235, 0.9);
|
|
}
|
|
.copy-content {
|
|
max-height: 120px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
border: 1px solid rgba(42, 37, 32, 0.08);
|
|
background: rgba(250, 247, 240, 0.74) !important;
|
|
}
|
|
.copy-content::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 30px;
|
|
background: linear-gradient(transparent, rgba(250, 247, 240, 0.96));
|
|
}
|
|
.rating-stars i {
|
|
color: #dee2e6;
|
|
cursor: pointer;
|
|
font-size: 1.2rem;
|
|
margin-right: 2px;
|
|
transition: color 0.2s;
|
|
}
|
|
.rating-stars i.active,
|
|
.rating-stars i:hover {
|
|
color: #ffc107;
|
|
}
|
|
.badge-type {
|
|
font-size: 0.7rem;
|
|
font-weight: 500;
|
|
}
|
|
.keyword-badge {
|
|
font-size: 0.75rem;
|
|
padding: 0.25rem 0.5rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.ai-history-hero {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.ai-history-actions,
|
|
.ai-history-action-btn {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
let currentPage = 1;
|
|
let perPage = 10;
|
|
let selectedIds = new Set();
|
|
let currentRating = 0;
|
|
|
|
// 頁面載入
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadStatistics();
|
|
loadHistory();
|
|
|
|
// 搜尋防抖
|
|
let searchTimeout;
|
|
document.getElementById('searchInput').addEventListener('input', function() {
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
currentPage = 1;
|
|
loadHistory();
|
|
}, 300);
|
|
});
|
|
|
|
// 篩選變更
|
|
['filterType', 'filterFavorite', 'filterStartDate', 'filterEndDate'].forEach(id => {
|
|
document.getElementById(id).addEventListener('change', () => {
|
|
currentPage = 1;
|
|
loadHistory();
|
|
});
|
|
});
|
|
|
|
// 全選
|
|
document.getElementById('selectAll').addEventListener('change', function() {
|
|
document.querySelectorAll('.history-checkbox').forEach(cb => {
|
|
cb.checked = this.checked;
|
|
updateSelection(parseInt(cb.dataset.id), this.checked);
|
|
});
|
|
});
|
|
|
|
// 評分星星點擊
|
|
document.querySelectorAll('#editRating i').forEach(star => {
|
|
star.addEventListener('click', function() {
|
|
currentRating = parseInt(this.dataset.rating);
|
|
updateStars(currentRating);
|
|
});
|
|
star.addEventListener('mouseenter', function() {
|
|
updateStars(parseInt(this.dataset.rating));
|
|
});
|
|
});
|
|
document.getElementById('editRating').addEventListener('mouseleave', function() {
|
|
updateStars(currentRating);
|
|
});
|
|
});
|
|
|
|
// 載入統計資料
|
|
async function loadStatistics() {
|
|
try {
|
|
const response = await fetch('/api/ai/statistics?days=30');
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
const stats = result.data;
|
|
document.getElementById('statTotal').textContent = stats.total_count;
|
|
document.getElementById('statFavorite').textContent = stats.favorite_count;
|
|
document.getElementById('statUsed').textContent = stats.used_count;
|
|
document.getElementById('statDuration').textContent = stats.avg_duration;
|
|
}
|
|
} catch (error) {
|
|
console.error('載入統計失敗:', error);
|
|
}
|
|
}
|
|
|
|
// 載入歷史記錄
|
|
async function loadHistory() {
|
|
const params = new URLSearchParams({
|
|
page: currentPage,
|
|
per_page: perPage
|
|
});
|
|
|
|
const search = document.getElementById('searchInput').value;
|
|
const type = document.getElementById('filterType').value;
|
|
const favorite = document.getElementById('filterFavorite').value;
|
|
const startDate = document.getElementById('filterStartDate').value;
|
|
const endDate = document.getElementById('filterEndDate').value;
|
|
|
|
if (search) params.append('search', search);
|
|
if (type) params.append('type', type);
|
|
if (favorite) params.append('favorite', favorite);
|
|
if (startDate) params.append('start_date', startDate);
|
|
if (endDate) params.append('end_date', endDate);
|
|
|
|
try {
|
|
const response = await fetch(`/api/ai/history?${params}`);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
renderHistory(result.data.items);
|
|
renderPagination(result.data);
|
|
document.getElementById('totalCount').textContent = result.data.total;
|
|
}
|
|
} catch (error) {
|
|
console.error('載入歷史失敗:', error);
|
|
document.getElementById('historyList').innerHTML = `
|
|
<div class="alert alert-danger">
|
|
<i class="fas fa-exclamation-circle me-2"></i>載入失敗,請稍後再試
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 渲染歷史列表
|
|
function renderHistory(items) {
|
|
const container = document.getElementById('historyList');
|
|
|
|
if (items.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
|
|
<p class="text-muted">尚無可回收文案</p>
|
|
<a href="/ai_recommend" class="btn btn-primary">
|
|
<i class="fas fa-magic me-1"></i>產生銷售動作
|
|
</a>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = items.map(item => {
|
|
const typeLabels = {
|
|
'copy': { text: '銷售文案', class: 'bg-primary' },
|
|
'recommend': { text: '商品推薦', class: 'bg-success' },
|
|
'weather_analysis': { text: '天氣分析', class: 'bg-info' }
|
|
};
|
|
const typeInfo = typeLabels[item.generation_type] || { text: item.generation_type, class: 'bg-secondary' };
|
|
|
|
const keywords = item.input_keywords || [];
|
|
const date = new Date(item.created_at).toLocaleString('zh-TW');
|
|
|
|
return `
|
|
<div class="card history-card mb-3 ${selectedIds.has(item.id) ? 'selected' : ''}" data-id="${item.id}">
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-auto d-flex align-items-start">
|
|
<input type="checkbox" class="form-check-input history-checkbox mt-1"
|
|
data-id="${item.id}" ${selectedIds.has(item.id) ? 'checked' : ''}
|
|
onchange="updateSelection(${item.id}, this.checked)">
|
|
</div>
|
|
<div class="col">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<div>
|
|
<span class="badge ${typeInfo.class} badge-type me-2">${typeInfo.text}</span>
|
|
<strong class="fs-5">${escapeHtml(item.product_name || '未命名')}</strong>
|
|
${item.is_favorite ? '<i class="fas fa-star text-warning ms-2"></i>' : ''}
|
|
${item.is_used ? '<span class="badge bg-success-subtle text-success ms-2">已使用</span>' : ''}
|
|
</div>
|
|
<div class="text-muted small">
|
|
<i class="fas fa-clock me-1"></i>${date}
|
|
</div>
|
|
</div>
|
|
|
|
${keywords.length > 0 ? `
|
|
<div class="mb-2">
|
|
${keywords.map(k => `<span class="badge bg-light text-dark keyword-badge me-1">#${escapeHtml(k)}</span>`).join('')}
|
|
</div>
|
|
` : ''}
|
|
|
|
<div class="copy-content bg-light rounded p-3 mb-3">
|
|
<p class="mb-0 text-break">${escapeHtml(item.output_content)}</p>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div class="small text-muted">
|
|
<span class="me-3"><i class="fas fa-wand-magic-sparkles me-1"></i>建議引擎</span>
|
|
${item.generation_duration ? `<span><i class="fas fa-stopwatch me-1"></i>${item.generation_duration}秒</span>` : ''}
|
|
${item.input_style ? `<span class="ms-3"><i class="fas fa-palette me-1"></i>${item.input_style}</span>` : ''}
|
|
</div>
|
|
<div class="btn-group btn-group-sm">
|
|
<button class="btn btn-outline-secondary" onclick="copyContent('${escapeAttr(item.output_content)}')" title="複製">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
<button class="btn btn-outline-warning" onclick="toggleFavorite(${item.id})" title="${item.is_favorite ? '取消收藏' : '收藏'}">
|
|
<i class="fas fa-star"></i>
|
|
</button>
|
|
<button class="btn btn-outline-primary" onclick="openEdit(${item.id})" title="編輯">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button class="btn btn-outline-danger" onclick="deleteItem(${item.id})" title="刪除">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
}
|
|
|
|
// 渲染分頁
|
|
function renderPagination(data) {
|
|
const pagination = document.getElementById('pagination');
|
|
const totalPages = data.total_pages;
|
|
|
|
if (totalPages <= 1) {
|
|
pagination.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
// 上一頁
|
|
html += `<li class="page-item ${currentPage === 1 ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="goToPage(${currentPage - 1})">
|
|
<i class="fas fa-chevron-left"></i>
|
|
</a>
|
|
</li>`;
|
|
|
|
// 頁碼
|
|
const startPage = Math.max(1, currentPage - 2);
|
|
const endPage = Math.min(totalPages, currentPage + 2);
|
|
|
|
if (startPage > 1) {
|
|
html += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(1)">1</a></li>`;
|
|
if (startPage > 2) {
|
|
html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
|
|
}
|
|
}
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
html += `<li class="page-item ${i === currentPage ? 'active' : ''}">
|
|
<a class="page-link" href="#" onclick="goToPage(${i})">${i}</a>
|
|
</li>`;
|
|
}
|
|
|
|
if (endPage < totalPages) {
|
|
if (endPage < totalPages - 1) {
|
|
html += `<li class="page-item disabled"><span class="page-link">...</span></li>`;
|
|
}
|
|
html += `<li class="page-item"><a class="page-link" href="#" onclick="goToPage(${totalPages})">${totalPages}</a></li>`;
|
|
}
|
|
|
|
// 下一頁
|
|
html += `<li class="page-item ${currentPage === totalPages ? 'disabled' : ''}">
|
|
<a class="page-link" href="#" onclick="goToPage(${currentPage + 1})">
|
|
<i class="fas fa-chevron-right"></i>
|
|
</a>
|
|
</li>`;
|
|
|
|
pagination.innerHTML = html;
|
|
}
|
|
|
|
function goToPage(page) {
|
|
currentPage = page;
|
|
loadHistory();
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
}
|
|
|
|
// 選擇管理
|
|
function updateSelection(id, checked) {
|
|
if (checked) {
|
|
selectedIds.add(id);
|
|
} else {
|
|
selectedIds.delete(id);
|
|
}
|
|
|
|
const count = selectedIds.size;
|
|
document.getElementById('selectedCount').textContent = count;
|
|
document.getElementById('batchDeleteBtn').disabled = count === 0;
|
|
|
|
// 更新卡片樣式
|
|
const card = document.querySelector(`.history-card[data-id="${id}"]`);
|
|
if (card) {
|
|
card.classList.toggle('selected', checked);
|
|
}
|
|
}
|
|
|
|
// 複製內容
|
|
function copyContent(content) {
|
|
navigator.clipboard.writeText(content).then(() => {
|
|
const toast = new bootstrap.Toast(document.getElementById('copyToast'));
|
|
toast.show();
|
|
});
|
|
}
|
|
|
|
// 切換收藏
|
|
async function toggleFavorite(id) {
|
|
try {
|
|
const response = await fetch(`/api/ai/history/${id}/favorite`, { method: 'POST' });
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
loadHistory();
|
|
loadStatistics();
|
|
}
|
|
} catch (error) {
|
|
console.error('切換收藏失敗:', error);
|
|
}
|
|
}
|
|
|
|
// 開啟編輯
|
|
async function openEdit(id) {
|
|
try {
|
|
const response = await fetch(`/api/ai/history/${id}`);
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
const item = result.data;
|
|
document.getElementById('editId').value = item.id;
|
|
document.getElementById('editProductName').value = item.product_name || '';
|
|
document.getElementById('editContent').value = item.output_content;
|
|
document.getElementById('editFavorite').checked = item.is_favorite;
|
|
document.getElementById('editUsed').checked = item.is_used;
|
|
document.getElementById('editNotes').value = item.notes || '';
|
|
|
|
currentRating = item.rating || 0;
|
|
updateStars(currentRating);
|
|
|
|
new bootstrap.Modal(document.getElementById('editModal')).show();
|
|
}
|
|
} catch (error) {
|
|
console.error('載入詳情失敗:', error);
|
|
}
|
|
}
|
|
|
|
function updateStars(rating) {
|
|
document.querySelectorAll('#editRating i').forEach(star => {
|
|
star.classList.toggle('active', parseInt(star.dataset.rating) <= rating);
|
|
});
|
|
}
|
|
|
|
// 儲存編輯
|
|
async function saveEdit() {
|
|
const id = document.getElementById('editId').value;
|
|
|
|
try {
|
|
const response = await fetch(`/api/ai/history/${id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
output_content: document.getElementById('editContent').value,
|
|
rating: currentRating || null,
|
|
is_favorite: document.getElementById('editFavorite').checked,
|
|
is_used: document.getElementById('editUsed').checked,
|
|
notes: document.getElementById('editNotes').value
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
bootstrap.Modal.getInstance(document.getElementById('editModal')).hide();
|
|
loadHistory();
|
|
loadStatistics();
|
|
} else {
|
|
alert('儲存失敗: ' + result.error);
|
|
}
|
|
} catch (error) {
|
|
console.error('儲存失敗:', error);
|
|
alert('儲存失敗');
|
|
}
|
|
}
|
|
|
|
// 刪除單筆
|
|
async function deleteItem(id) {
|
|
if (!confirm('確定要刪除這筆記錄嗎?')) return;
|
|
|
|
try {
|
|
const response = await fetch(`/api/ai/history/${id}`, { method: 'DELETE' });
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
loadHistory();
|
|
loadStatistics();
|
|
}
|
|
} catch (error) {
|
|
console.error('刪除失敗:', error);
|
|
}
|
|
}
|
|
|
|
// 批次刪除
|
|
async function batchDelete() {
|
|
if (!confirm(`確定要刪除選取的 ${selectedIds.size} 筆記錄嗎?`)) return;
|
|
|
|
try {
|
|
const response = await fetch('/api/ai/history/batch', {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ ids: Array.from(selectedIds) })
|
|
});
|
|
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
selectedIds.clear();
|
|
document.getElementById('selectedCount').textContent = '0';
|
|
document.getElementById('batchDeleteBtn').disabled = true;
|
|
document.getElementById('selectAll').checked = false;
|
|
loadHistory();
|
|
loadStatistics();
|
|
}
|
|
} catch (error) {
|
|
console.error('批次刪除失敗:', error);
|
|
}
|
|
}
|
|
|
|
// 重置篩選
|
|
function resetFilters() {
|
|
document.getElementById('searchInput').value = '';
|
|
document.getElementById('filterType').value = '';
|
|
document.getElementById('filterFavorite').value = '';
|
|
document.getElementById('filterStartDate').value = '';
|
|
document.getElementById('filterEndDate').value = '';
|
|
currentPage = 1;
|
|
loadHistory();
|
|
}
|
|
|
|
// 工具函數
|
|
function escapeHtml(text) {
|
|
if (!text) return '';
|
|
return String(text)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function escapeAttr(text) {
|
|
if (!text) return '';
|
|
return String(text)
|
|
.replace(/\\/g, '\\\\')
|
|
.replace(/'/g, "\\'")
|
|
.replace(/"/g, '\\"')
|
|
.replace(/\n/g, '\\n')
|
|
.replace(/\r/g, '\\r');
|
|
}
|
|
</script>
|
|
{% endblock extra_js %}
|