Files
PlantAI-dev/apps/web/vue3/index-with-sidebar.html
2026-08-03 18:42:47 +08:00

386 lines
16 KiB
HTML
Executable File

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BotanicalBaseAgent - 基因家族分析</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-plus@2.3.14/dist/index.css">
<script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/element-plus@2.3.14/dist/index.full.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@element-plus/icons-vue@2.1.0/dist/index.iife.js"></script>
<link rel="stylesheet" href="conversation-sidebar.css">
<style>
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Helvetica Neue', Arial, sans-serif;
background: #f5f7fa;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px 20px;
}
.header h1 { margin: 0; font-size: 22px; }
.main-container {
display: flex;
height: calc(100vh - 60px);
overflow: hidden;
}
/* 左侧控制面板 */
.left-panel {
width: 280px;
background: white;
border-right: 1px solid #e4e7ed;
display: flex;
flex-direction: column;
padding: 15px;
gap: 15px;
overflow-y: auto;
}
/* 中间内容区 */
.center-panel {
flex: 1;
display: flex;
flex-direction: column;
padding: 15px;
gap: 15px;
overflow: hidden;
}
/* 右侧结果区 */
.right-panel {
width: 350px;
background: white;
border-left: 1px solid #e4e7ed;
padding: 15px;
overflow-y: auto;
}
.panel-section {
background: white;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.panel-title {
font-size: 14px;
font-weight: bold;
color: #409EFF;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #409EFF;
}
.chat-area {
flex: 1;
background: white;
border-radius: 8px;
padding: 15px;
overflow-y: auto;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.chat-message {
margin-bottom: 15px;
padding: 12px 16px;
border-radius: 8px;
max-width: 80%;
}
.chat-message.user {
background: #ecf5ff;
margin-left: auto;
text-align: right;
}
.chat-message.assistant {
background: #f0f9eb;
}
.input-area {
display: flex;
gap: 10px;
}
.stage-tag {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
margin: 4px;
}
.stage-tag.success { background: #67C23A; color: white; }
.stage-tag.running { background: #E6A23C; color: white; }
.stage-tag.pending { background: #909399; color: white; }
</style>
</head>
<body>
<div id="app">
<!-- Header -->
<div class="header">
<h1>🌱 BotanicalBaseAgent - 基因家族分析系统</h1>
</div>
<div class="main-container">
<!-- 左侧:会话侧边栏 + 控制面板 -->
<div class="left-panel">
<!-- 会话侧边栏 -->
<div class="conversation-sidebar" style="width: 100%; margin: -15px; margin-bottom: 0;">
<div class="sidebar-header">
<button class="new-chat-btn" @click="createNewConversation" style="flex: 1;">
<el-icon><plus /></el-icon>
<span>New Chat</span>
</button>
</div>
<div class="search-box" style="padding: 12px;">
<el-input
v-model="searchQuery"
placeholder="搜索会话..."
prefix-icon="search"
clearable
size="small">
</el-input>
</div>
<div class="conversation-list" style="max-height: 300px;">
<div class="list-section">
<div class="section-title">会话列表</div>
<div v-for="conv in filteredConversations"
:key="conv.id"
class="conversation-item"
:class="{ active: currentConv?.id === conv.id }"
@click="selectConversation(conv)">
<div class="item-icon">
<el-icon><chat-dot-round /></el-icon>
</div>
<div class="item-content">
<div class="item-title">{{ conv.title }}</div>
<div class="item-meta">
<span class="gene-family" v-if="conv.gene_family">{{ conv.gene_family }}</span>
<span class="message-count">{{ conv.message_count }}条消息</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 分析控制 -->
<div class="panel-section">
<div class="panel-title">▶ Analysis Stage</div>
<el-select v-model="selectedStage" placeholder="选择分析阶段" style="width: 100%;">
<el-option label="Identify - 基因家族鉴定" value="identify"></el-option>
<el-option label="Phylogeny - 系统发育分析" value="phylogeny"></el-option>
<el-option label="Motif - Motif分析" value="motif"></el-option>
<el-option label="Domain - 结构域分析" value="domain"></el-option>
<el-option label="Expression - 表达分析" value="expression"></el-option>
</el-select>
<el-button type="primary" style="width: 100%; margin-top: 10px;" @click="executeAnalysis">
🚀 执行分析
</el-button>
</div>
<!-- 参数设置 -->
<div class="panel-section">
<div class="panel-title">⚙️ 参数设置</div>
<el-form :model="params" label-width="80px" size="small">
<el-form-item label="前缀">
<el-input v-model="params.prefix" placeholder="如: Ft.SPL"></el-input>
</el-form-item>
<el-form-item label="CPU">
<el-input-number v-model="params.cpu" :min="1" :max="32" style="width: 100%;"></el-input-number>
</el-form-item>
</el-form>
</div>
</div>
<!-- 中间:对话区 -->
<div class="center-panel">
<div class="chat-area">
<div v-for="(msg, index) in messages" :key="index"
class="chat-message" :class="msg.role">
<div style="font-size: 12px; color: #666; margin-bottom: 4px;">
{{ msg.role === 'user' ? '用户' : 'AI助手' }} · {{ msg.time }}
</div>
<div>{{ msg.content }}</div>
</div>
<div v-if="messages.length === 0" style="text-align: center; color: #999; padding: 40px;">
<el-icon :size="48" style="display: block; margin-bottom: 10px;"><chat-line-round /></el-icon>
<p>开始一个新的对话</p>
<p style="font-size: 12px;">或点击左侧会话继续之前的对话</p>
</div>
</div>
<div class="input-area">
<el-input
v-model="userInput"
type="textarea"
:rows="3"
placeholder="描述您的需求,如:帮我分析拟南芥SPL基因家族"
@keyup.enter.ctrl="sendMessage">
</el-input>
<el-button type="primary" size="large" @click="sendMessage" :disabled="!userInput.trim()">
发送
</el-button>
</div>
</div>
<!-- 右侧:结果展示 -->
<div class="right-panel">
<div class="panel-title">📊 Analysis Results</div>
<div v-for="stage in stages" :key="stage.name" style="margin-bottom: 15px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-weight: 500;">{{ stage.label }}</span>
<span class="stage-tag" :class="stage.status">{{ stage.statusText }}</span>
</div>
<el-progress :percentage="stage.progress" :status="stage.status"></el-progress>
</div>
<el-divider></el-divider>
<div style="text-align: center; color: #999; padding: 20px;">
<el-icon :size="48" style="display: block; margin-bottom: 10px;"><picture /></el-icon>
<p>分析结果将在此显示</p>
</div>
</div>
</div>
</div>
<script>
const { createApp, ref, reactive, computed } = Vue;
const app = createApp({
setup() {
// 会话管理
const conversations = ref([
{ id: 1, title: 'Ft.SPL基因家族分析', gene_family: 'SPL', message_count: 12 },
{ id: 2, title: 'Os.TCP进化分析', gene_family: 'TCP', message_count: 5 },
{ id: 3, title: 'HMMER参数配置咨询', gene_family: null, message_count: 8 },
]);
const currentConv = ref(null);
const searchQuery = ref('');
const filteredConversations = computed(() => {
if (!searchQuery.value) return conversations.value;
const query = searchQuery.value.toLowerCase();
return conversations.value.filter(conv =>
conv.title.toLowerCase().includes(query) ||
(conv.gene_family && conv.gene_family.toLowerCase().includes(query))
);
});
const createNewConversation = () => {
const newConv = {
id: Date.now(),
title: '新会话 ' + new Date().toLocaleTimeString(),
gene_family: null,
message_count: 0
};
conversations.value.unshift(newConv);
selectConversation(newConv);
};
const selectConversation = (conv) => {
currentConv.value = conv;
// 清空消息并添加欢迎语
messages.value = [{
role: 'assistant',
content: `欢迎来到 "${conv.title}"!我是BotanicalBaseAgent,专注于基因家族分析。请告诉我您想分析哪个基因家族?`,
time: new Date().toLocaleTimeString()
}];
};
// 分析参数
const selectedStage = ref('');
const params = reactive({
prefix: 'Ft.SPL',
cpu: 8
});
// 消息
const messages = ref([]);
const userInput = ref('');
const sendMessage = () => {
if (!userInput.value.trim()) return;
// 添加用户消息
messages.value.push({
role: 'user',
content: userInput.value,
time: new Date().toLocaleTimeString()
});
// 更新当前会话消息数
if (currentConv.value) {
currentConv.value.message_count++;
}
// 模拟AI回复
setTimeout(() => {
messages.value.push({
role: 'assistant',
content: `收到您的请求:"${userInput.value}"。我将帮您分析基因家族。请确认分析参数后点击"执行分析"。`,
time: new Date().toLocaleTimeString()
});
if (currentConv.value) {
currentConv.value.message_count++;
}
}, 500);
userInput.value = '';
};
// 分析阶段
const stages = ref([
{ name: 'identify', label: 'Identify', status: 'success', statusText: '已完成', progress: 100 },
{ name: 'phylogeny', label: 'Phylogeny', status: 'running', statusText: '运行中', progress: 60 },
{ name: 'motif', label: 'Motif', status: 'pending', statusText: '等待中', progress: 0 },
{ name: 'domain', label: 'Domain', status: 'pending', statusText: '等待中', progress: 0 },
]);
const executeAnalysis = () => {
if (!selectedStage.value) {
ElementPlus.ElMessage.warning('请先选择分析阶段');
return;
}
ElementPlus.ElMessage.success(`开始执行 ${selectedStage.value} 分析`);
};
return {
conversations,
currentConv,
searchQuery,
filteredConversations,
createNewConversation,
selectConversation,
selectedStage,
params,
messages,
userInput,
sendMessage,
stages,
executeAnalysis
};
}
});
app.use(ElementPlus);
// 注册图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.mount('#app');
</script>
</body>
</html>