qwen_agent/public/bot-manager.html
2026-01-16 23:05:30 +08:00

910 lines
29 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>Bot Manager</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<style>
:root {
--primary: #2563EB;
--primary-hover: #1D4ED8;
--secondary: #3B82F6;
--background: #F8FAFC;
--surface: #FFFFFF;
--text: #1E293B;
--text-muted: #64748B;
--border: #E2E8F0;
--success: #10B981;
--warning: #F59E0B;
--error: #EF4444;
--glass-bg: rgba(255, 255, 255, 0.8);
--glass-blur: 12px;
}
.dark {
--primary: #3B82F6;
--primary-hover: #60A5FA;
--secondary: #60A5FA;
--background: #0F172A;
--surface: #1E293B;
--text: #F1F5F9;
--text-muted: #94A3B8;
--border: #334155;
--glass-bg: rgba(30, 41, 59, 0.8);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Open Sans', sans-serif;
background: var(--background);
color: var(--text);
min-height: 100vh;
transition: background-color 0.3s ease, color 0.3s ease;
}
/* ===== Header ===== */
.header {
height: 64px;
background: var(--glass-bg);
backdrop-filter: blur(var(--glass-blur));
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px;
position: sticky;
top: 0;
z-index: 10;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.header-logo {
width: 36px;
height: 36px;
border-radius: 10px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.header-title {
font-family: 'Poppins', sans-serif;
font-size: 18px;
font-weight: 600;
}
.header-right {
display: flex;
align-items: center;
gap: 12px;
}
.header-btn {
width: 38px;
height: 38px;
border-radius: 10px;
border: none;
background: transparent;
color: var(--text);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease;
}
.header-btn:hover {
background: var(--background);
}
.header-btn.primary {
background: var(--primary);
color: white;
padding: 0 16px;
width: auto;
gap: 8px;
}
.header-btn.primary:hover {
background: var(--primary-hover);
}
/* ===== Main Content ===== */
.main-content {
max-width: 1200px;
margin: 0 auto;
padding: 32px 24px;
}
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.page-title {
font-family: 'Poppins', sans-serif;
font-size: 24px;
font-weight: 600;
}
.page-subtitle {
font-size: 14px;
color: var(--text-muted);
margin-top: 4px;
}
/* ===== Bot Grid ===== */
.bot-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
}
.bot-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 16px;
padding: 20px;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
}
.bot-card:hover {
border-color: var(--primary);
box-shadow: 0 4px 20px rgba(37, 99, 235, 0.1);
transform: translateY(-2px);
}
.bot-card-header {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 14px;
}
.bot-card-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, var(--primary), var(--secondary));
display: flex;
align-items: center;
justify-content: center;
color: white;
flex-shrink: 0;
}
.bot-card-info {
flex: 1;
min-width: 0;
}
.bot-card-name {
font-family: 'Poppins', sans-serif;
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.bot-card-id {
font-size: 12px;
color: var(--text-muted);
font-family: 'Monaco', 'Consolas', monospace;
background: var(--background);
padding: 2px 6px;
border-radius: 4px;
display: inline-block;
}
.bot-card-meta {
display: flex;
gap: 16px;
font-size: 12px;
color: var(--text-muted);
}
.bot-card-meta-item {
display: flex;
align-items: center;
gap: 4px;
}
.bot-card-actions {
display: flex;
gap: 8px;
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid var(--border);
}
.bot-card-action {
flex: 1;
padding: 8px 12px;
border-radius: 8px;
border: 1px solid var(--border);
background: transparent;
color: var(--text);
font-size: 13px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
transition: all 0.15s ease;
}
.bot-card-action:hover {
background: var(--background);
border-color: var(--primary);
}
.bot-card-action.delete:hover {
background: rgba(239, 68, 68, 0.1);
border-color: var(--error);
color: var(--error);
}
/* ===== Empty State ===== */
.empty-state {
text-align: center;
padding: 80px 20px;
}
.empty-state-icon {
width: 80px;
height: 80px;
margin: 0 auto 20px;
border-radius: 20px;
background: var(--background);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
}
.empty-state-title {
font-family: 'Poppins', sans-serif;
font-size: 20px;
font-weight: 600;
margin-bottom: 8px;
}
.empty-state-subtitle {
font-size: 14px;
color: var(--text-muted);
margin-bottom: 24px;
}
/* ===== Modal ===== */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: none;
align-items: center;
justify-content: center;
z-index: 100;
}
.modal-overlay.active {
display: flex;
}
.modal {
background: var(--surface);
border-radius: 16px;
width: 90%;
max-width: 480px;
max-height: 85vh;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}
.modal-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-title {
font-family: 'Poppins', sans-serif;
font-size: 18px;
font-weight: 600;
}
.modal-close {
width: 32px;
height: 32px;
border-radius: 8px;
border: none;
background: transparent;
color: var(--text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.modal-close:hover {
background: var(--background);
color: var(--text);
}
.modal-body {
padding: 24px;
}
.form-group {
margin-bottom: 18px;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
margin-bottom: 8px;
}
.form-input {
width: 100%;
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 14px;
background: var(--background);
color: var(--text);
outline: none;
transition: border-color 0.15s ease;
}
.form-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.form-hint {
font-size: 12px;
color: var(--text-muted);
margin-top: 6px;
}
.modal-footer {
padding: 16px 24px;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn {
padding: 10px 20px;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-secondary {
background: var(--background);
border: 1px solid var(--border);
color: var(--text);
}
.btn-secondary:hover {
border-color: var(--primary);
}
.btn-primary {
background: var(--primary);
border: none;
color: white;
}
.btn-primary:hover {
background: var(--primary-hover);
}
/* ===== Delete Confirmation Modal ===== */
.delete-confirm-content {
text-align: center;
padding: 20px 0;
}
.delete-confirm-icon {
width: 56px;
height: 56px;
margin: 0 auto 16px;
border-radius: 50%;
background: rgba(239, 68, 68, 0.1);
display: flex;
align-items: center;
justify-content: center;
color: var(--error);
}
.delete-confirm-title {
font-family: 'Poppins', sans-serif;
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.delete-confirm-text {
font-size: 14px;
color: var(--text-muted);
}
/* ===== Scrollbar ===== */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
/* ===== Responsive ===== */
@media (max-width: 768px) {
.header {
padding: 0 16px;
}
.main-content {
padding: 20px 16px;
}
.bot-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.header-btn.primary span {
display: none;
}
}
</style>
</head>
<body>
<!-- Header -->
<header class="header">
<div class="header-left">
<div class="header-logo">
<i data-lucide="bot" style="width: 20px; height: 20px;"></i>
</div>
<div>
<div class="header-title">Bot Manager</div>
</div>
</div>
<div class="header-right">
<button class="header-btn" id="theme-toggle" title="切换主题">
<i data-lucide="moon" style="width: 18px; height: 18px;"></i>
</button>
<button class="header-btn primary" id="add-bot-btn">
<i data-lucide="plus" style="width: 16px; height: 16px;"></i>
<span>新建 Bot</span>
</button>
</div>
</header>
<!-- Main Content -->
<main class="main-content">
<div class="page-header">
<div>
<h1 class="page-title">我的 Bots</h1>
<p class="page-subtitle">管理您的 AI 聊天机器人配置</p>
</div>
</div>
<!-- Bot Grid -->
<div class="bot-grid" id="bot-grid">
<!-- 动态生成 -->
</div>
</main>
<!-- Add/Edit Bot Modal -->
<div class="modal-overlay" id="bot-modal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title" id="bot-modal-title">新建 Bot</h3>
<button class="modal-close" id="bot-modal-close">
<i data-lucide="x" style="width: 18px; height: 18px;"></i>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label" for="bot-name">Bot 名称 *</label>
<input type="text" id="bot-name" class="form-input" placeholder="例如:客服助手、销售顾问">
</div>
<div class="form-group">
<label class="form-label" for="bot-id-input">Bot ID *</label>
<input type="text" id="bot-id-input" class="form-input" placeholder="例如test 或 UUID">
<p class="form-hint">这是用于 API 调用的唯一标识符</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="bot-modal-cancel">取消</button>
<button class="btn btn-primary" id="bot-modal-save">保存</button>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal-overlay" id="delete-modal">
<div class="modal" style="max-width: 400px;">
<div class="modal-body">
<div class="delete-confirm-content">
<div class="delete-confirm-icon">
<i data-lucide="alert-triangle" style="width: 28px; height: 28px;"></i>
</div>
<h3 class="delete-confirm-title">确认删除</h3>
<p class="delete-confirm-text">确定要删除这个 Bot 吗?此操作无法撤销。</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="delete-modal-cancel">取消</button>
<button class="btn btn-primary" id="delete-modal-confirm" style="background: var(--error);">删除</button>
</div>
</div>
</div>
<script>
// Initialize Lucide icons
lucide.createIcons();
class BotManager {
constructor() {
this.elements = {
themeToggle: document.getElementById('theme-toggle'),
addBotBtn: document.getElementById('add-bot-btn'),
botGrid: document.getElementById('bot-grid'),
// Bot Modal
botModal: document.getElementById('bot-modal'),
botModalTitle: document.getElementById('bot-modal-title'),
botModalClose: document.getElementById('bot-modal-close'),
botModalCancel: document.getElementById('bot-modal-cancel'),
botModalSave: document.getElementById('bot-modal-save'),
botNameInput: document.getElementById('bot-name'),
botIdInput: document.getElementById('bot-id-input'),
// Delete Modal
deleteModal: document.getElementById('delete-modal'),
deleteModalCancel: document.getElementById('delete-modal-cancel'),
deleteModalConfirm: document.getElementById('delete-modal-confirm')
};
this.bots = [];
this.editingBotId = null;
this.deletingBotId = null;
this.initializeEventListeners();
this.loadTheme();
this.loadBots();
}
initializeEventListeners() {
// Theme toggle
this.elements.themeToggle.addEventListener('click', () => this.toggleTheme());
// Add bot
this.elements.addBotBtn.addEventListener('click', () => this.openBotModal());
// Bot modal
this.elements.botModalClose.addEventListener('click', () => this.closeBotModal());
this.elements.botModalCancel.addEventListener('click', () => this.closeBotModal());
this.elements.botModalSave.addEventListener('click', () => this.saveBot());
this.elements.botModal.addEventListener('click', (e) => {
if (e.target === this.elements.botModal) this.closeBotModal();
});
// Delete modal
this.elements.deleteModalCancel.addEventListener('click', () => this.closeDeleteModal());
this.elements.deleteModalConfirm.addEventListener('click', () => this.confirmDelete());
}
loadTheme() {
const theme = localStorage.getItem('theme') || 'light';
if (theme === 'dark') {
document.documentElement.classList.add('dark');
}
this.updateThemeIcon(theme === 'dark');
}
toggleTheme() {
document.documentElement.classList.toggle('dark');
const isDark = document.documentElement.classList.contains('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
this.updateThemeIcon(isDark);
}
updateThemeIcon(isDark) {
const icon = this.elements.themeToggle.querySelector('i');
if (!icon) return;
icon.setAttribute('data-lucide', isDark ? 'sun' : 'moon');
lucide.createIcons();
}
loadBots() {
const stored = localStorage.getItem('bot-list');
if (stored) {
try {
this.bots = JSON.parse(stored);
} catch (e) {
this.bots = [];
}
}
this.renderBots();
}
saveBots() {
localStorage.setItem('bot-list', JSON.stringify(this.bots));
}
renderBots() {
const grid = this.elements.botGrid;
if (this.bots.length === 0) {
grid.innerHTML = `
<div class="empty-state" style="grid-column: 1 / -1;">
<div class="empty-state-icon">
<i data-lucide="bot" style="width: 40px; height: 40px;"></i>
</div>
<h2 class="empty-state-title">暂无 Bots</h2>
<p class="empty-state-subtitle">点击上方按钮创建您的第一个 Bot</p>
</div>
`;
lucide.createIcons();
return;
}
grid.innerHTML = '';
this.bots.forEach(bot => {
const card = this.createBotCard(bot);
grid.appendChild(card);
});
lucide.createIcons();
}
createBotCard(bot) {
const card = document.createElement('div');
card.className = 'bot-card';
const formatDate = (timestamp) => {
const date = new Date(timestamp);
return date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'short', day: 'numeric' });
};
card.innerHTML = `
<div class="bot-card-header">
<div class="bot-card-icon">
<i data-lucide="bot" style="width: 24px; height: 24px;"></i>
</div>
<div class="bot-card-info">
<div class="bot-card-name">${this.escapeHtml(bot.name)}</div>
<span class="bot-card-id">${this.escapeHtml(bot.botId)}</span>
</div>
</div>
<div class="bot-card-meta">
<span class="bot-card-meta-item">
<i data-lucide="calendar" style="width: 12px; height: 12px;"></i>
创建于 ${formatDate(bot.createdAt)}
</span>
<span class="bot-card-meta-item">
<i data-lucide="clock" style="width: 12px; height: 12px;"></i>
更新于 ${formatDate(bot.updatedAt)}
</span>
</div>
<div class="bot-card-actions">
<button class="bot-card-action" data-action="open" data-bot-id="${bot.id}">
<i data-lucide="external-link" style="width: 14px; height: 14px;"></i>
打开
</button>
<button class="bot-card-action" data-action="edit" data-bot-id="${bot.id}">
<i data-lucide="pencil" style="width: 14px; height: 14px;"></i>
编辑
</button>
<button class="bot-card-action delete" data-action="delete" data-bot-id="${bot.id}">
<i data-lucide="trash-2" style="width: 14px; height: 14px;"></i>
删除
</button>
</div>
`;
// Event listeners
card.addEventListener('click', (e) => {
const action = e.target.closest('[data-action]')?.dataset.action;
const botId = e.target.closest('[data-bot-id]')?.dataset.botId;
if (action === 'open' && botId) {
this.openBotChat(botId);
} else if (action === 'edit' && botId) {
e.stopPropagation();
this.openEditModal(botId);
} else if (action === 'delete' && botId) {
e.stopPropagation();
this.openDeleteModal(botId);
}
});
return card;
}
openBotChat(botInternalId) {
const bot = this.bots.find(b => b.id === botInternalId);
if (bot) {
// 保存当前选中的 bot ID
sessionStorage.setItem('current-bot-id', bot.id);
// 跳转到聊天页面
window.location.href = 'index.html';
}
}
openBotModal() {
this.editingBotId = null;
this.elements.botModalTitle.textContent = '新建 Bot';
this.elements.botNameInput.value = '';
this.elements.botIdInput.value = '';
this.elements.botModal.classList.add('active');
this.elements.botNameInput.focus();
}
openEditModal(botInternalId) {
const bot = this.bots.find(b => b.id === botInternalId);
if (!bot) return;
this.editingBotId = botInternalId;
this.elements.botModalTitle.textContent = '编辑 Bot';
this.elements.botNameInput.value = bot.name;
this.elements.botIdInput.value = bot.botId;
this.elements.botModal.classList.add('active');
this.elements.botNameInput.focus();
}
closeBotModal() {
this.elements.botModal.classList.remove('active');
this.editingBotId = null;
}
saveBot() {
const name = this.elements.botNameInput.value.trim();
const botId = this.elements.botIdInput.value.trim();
if (!name) {
alert('请输入 Bot 名称');
return;
}
if (!botId) {
alert('请输入 Bot ID');
return;
}
// 检查 Bot ID 是否重复
const existingBot = this.bots.find(b => b.botId === botId && b.id !== this.editingBotId);
if (existingBot) {
alert('Bot ID 已存在,请使用其他 ID');
return;
}
const now = Date.now();
if (this.editingBotId) {
// 编辑现有 bot
const bot = this.bots.find(b => b.id === this.editingBotId);
if (bot) {
bot.name = name;
bot.botId = botId;
bot.updatedAt = now;
}
} else {
// 新建 bot
const newBot = {
id: this.generateUUID(),
name: name,
botId: botId,
createdAt: now,
updatedAt: now
};
this.bots.unshift(newBot);
}
this.saveBots();
this.renderBots();
this.closeBotModal();
}
openDeleteModal(botInternalId) {
this.deletingBotId = botInternalId;
this.elements.deleteModal.classList.add('active');
}
closeDeleteModal() {
this.elements.deleteModal.classList.remove('active');
this.deletingBotId = null;
}
confirmDelete() {
if (!this.deletingBotId) return;
const bot = this.bots.find(b => b.id === this.deletingBotId);
if (bot) {
// 删除 bot 的设置数据
localStorage.removeItem(`bot-settings-${bot.botId}`);
}
this.bots = this.bots.filter(b => b.id !== this.deletingBotId);
this.saveBots();
this.renderBots();
this.closeDeleteModal();
}
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Initialize app
document.addEventListener('DOMContentLoaded', () => {
new BotManager();
});
</script>
</body>
</html>