Compare commits
4 Commits
577dceebfe
...
ac384ce10b
| Author | SHA1 | Date | |
|---|---|---|---|
| ac384ce10b | |||
| 950635d9a9 | |||
| d3100e8219 | |||
| c590aa21d0 |
304
docs/memory.html
Normal file
@@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#121212] text-gray-200 p-6">
|
||||
<!-- 顶部开关 -->
|
||||
<div class="flex justify-end items-center mb-6">
|
||||
<span class="text-gray-400 mr-3">记忆管理 已启用</span>
|
||||
<button class="w-12 h-6 bg-green-500 rounded-full relative">
|
||||
<span class="absolute right-1 top-1 w-4 h-4 bg-white rounded-full"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片区域 -->
|
||||
<div class="grid grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-white mb-1">15</div>
|
||||
<div class="text-sm text-gray-400">总记忆数</div>
|
||||
</div>
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-white mb-1">0.78</div>
|
||||
<div class="text-sm text-gray-400">平均分数</div>
|
||||
</div>
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-cyan-400 mb-1">6</div>
|
||||
<div class="text-sm text-gray-400">经验</div>
|
||||
</div>
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-red-400 mb-1">9</div>
|
||||
<div class="text-sm text-gray-400">经验教训</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="mb-4">
|
||||
<div class="relative">
|
||||
<svg class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索记忆内容..."
|
||||
class="w-full bg-[#0a0a0a] border border-gray-700 rounded-lg py-3 pl-10 pr-4 text-gray-200 focus:outline-none focus:border-gray-600"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选与操作栏 -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<select class="bg-[#0a0a0a] border border-gray-700 rounded-lg py-2 px-4 text-gray-200 focus:outline-none">
|
||||
<option>全部类型</option>
|
||||
<option>经验</option>
|
||||
<option>经验教训</option>
|
||||
</select>
|
||||
<button class="flex items-center space-x-2 bg-[#1e1e1e] border border-gray-700 rounded-lg py-2 px-4 text-gray-200 hover:bg-[#2a2a2a]">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<span>刷新</span>
|
||||
</button>
|
||||
<button class="flex items-center space-x-2 bg-indigo-600 rounded-lg py-2 px-4 text-white hover:bg-indigo-700">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
</svg>
|
||||
<span>LLM 智能审查</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="bg-[#1e1e1e] rounded-xl overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-[#0a0a0a]">
|
||||
<tr>
|
||||
<th class="w-10 px-4 py-3">
|
||||
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
|
||||
</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">类型</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">内容</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">分数</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">创建时间</th>
|
||||
<th class="px-4 py-3 text-left text-sm font-medium text-gray-400">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-800">
|
||||
<!-- 表格行 1 -->
|
||||
<tr class="hover:bg-[#252525]">
|
||||
<td class="px-4 py-4">
|
||||
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
|
||||
当任务明确要求获取外部信息(搜索最新数据、新闻、财报、行业报告等)时,模型必须主动调用搜索工具而非仅依赖内部知识。应在系统提示中明确要求「必须使用搜索工具获取XX最新数据」,首次迭代即调用搜索工具,采用「搜索→分析→输出」的递进式流程,避免模型陷入纯文字生成的空转循环。
|
||||
<div class="text-xs text-gray-500 mt-1">主体: 外部信息获取规范 · 属性: 工具调用原则</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-emerald-400 font-medium">0.95</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-400">3/10 15:35</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-gray-400 hover:text-white">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-red-400 hover:text-red-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 表格行 2 -->
|
||||
<tr class="hover:bg-[#252525]">
|
||||
<td class="px-4 py-4">
|
||||
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
|
||||
当任务明确要求获取外部信息(搜索最新数据、新闻、财报、行业报告等)时,模型必须调用搜索工具而非仅依赖内部知识。应在系统提示中明确要求「必须使用搜索工具获取XX最新数据」,并建立工具调用检测机制,确保任务执行路径正确。
|
||||
<div class="text-xs text-gray-500 mt-1">主体: 工具调用 · 属性: 错误教训</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-emerald-400 font-medium">0.95</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-400">3/10 12:34</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-gray-400 hover:text-white">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-red-400 hover:text-red-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 表格行 3 -->
|
||||
<tr class="hover:bg-[#252525]">
|
||||
<td class="px-4 py-4">
|
||||
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
|
||||
任务执行应采用「一次性完整输出」策略,避免分批次小幅输出导致的迭代空转。对于代码生成、文件写入等任务,应要求模型一次性完成方案设计与工具执行的完整流程,将确认性回复并入上一次响应,将冗余迭代压缩合并,复杂任务控制在2-3次迭代内完成。
|
||||
<div class="text-xs text-gray-500 mt-1">主体: 任务执行效率优化 · 属性: 迭代策略原则</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-emerald-400 font-medium">0.92</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-400">3/10 15:35</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-gray-400 hover:text-white">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-red-400 hover:text-red-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 表格行 4 -->
|
||||
<tr class="hover:bg-[#252525]">
|
||||
<td class="px-4 py-4">
|
||||
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
|
||||
任务执行过程中应建立工具调用检测机制,监控模型是否按要求调用了必要的工具。若迭代中工具调用数为0,说明执行路径不正确,应触发异常处理或提示,而非继续无效迭代。
|
||||
<div class="text-xs text-gray-500 mt-1">主体: 执行路径监控 · 属性: 质量控制机制</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-emerald-400 font-medium">0.88</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-400">3/10 15:35</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-gray-400 hover:text-white">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-red-400 hover:text-red-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 表格行 5 -->
|
||||
<tr class="hover:bg-[#252525]">
|
||||
<td class="px-4 py-4">
|
||||
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
|
||||
任务执行应采用「搜索→分析→输出」的递进式流程,避免无效迭代和空转。纯文字生成的重复迭代应压缩或合并,确认性回复应并入上一次响应。简单任务应在1-2次迭代内完成,避免模型陷入重复思考或无意义的自我确认。
|
||||
<div class="text-xs text-gray-500 mt-1">主体: 迭代效率 · 属性: 错误教训</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-emerald-400 font-medium">0.85</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-400">3/10 12:34</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-gray-400 hover:text-white">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-red-400 hover:text-red-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- 表格行 6 -->
|
||||
<tr class="hover:bg-[#252525]">
|
||||
<td class="px-4 py-4">
|
||||
<input type="checkbox" class="rounded bg-gray-700 border-gray-600">
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">经验</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-300 max-w-xl">
|
||||
对于代码生成或文件写入类任务,应要求模型一次性完整输出或按模块批量输出,避免分批次的小幅输出导致迭代次数过多、执行效率低下。
|
||||
<div class="text-xs text-gray-500 mt-1">主体: 代码生成策略 · 属性: 错误教训</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="text-amber-400 font-medium">0.80</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-gray-400">3/10 12:34</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-gray-400 hover:text-white">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-red-400 hover:text-red-300">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 底部文本区域 -->
|
||||
<div class="mt-6 px-4 text-gray-400 text-sm">
|
||||
<div class="mb-2">任务执行复盘发现问题:## 任务执行分析</div>
|
||||
<div class="mb-2">### 问题诊断</div>
|
||||
<div class="mb-2">**核心问题:模型在9次迭代中未执行任何工具调用**</div>
|
||||
<div>从迭代记录看,前9次迭代工具调用数均为0,说明模型一直在"空转"生成文字,未实际搜索最新数据。直到第10次才产出完整报告(包含2025年9月销量等最新数据),说明最后一次才真正调用了搜索工具。</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 这里可以添加交互逻辑
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #444;
|
||||
}
|
||||
</style>
|
||||
212
docs/plan.html
Normal file
@@ -0,0 +1,212 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>计划任务管理</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #444;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-[#0f0f0f] text-gray-200 min-h-screen font-sans">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="flex items-center justify-between px-4 py-2 border-b border-gray-800 bg-[#0a0a0a]">
|
||||
<!-- 左侧状态区 -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="text-gray-300 font-medium">default</span>
|
||||
<span class="flex items-center text-green-400 text-sm">
|
||||
<span class="w-2 h-2 rounded-full bg-green-400 mr-1.5"></span>
|
||||
运行中
|
||||
</span>
|
||||
<span class="bg-blue-600/20 text-blue-400 text-xs px-2 py-0.5 rounded">网页访问</span>
|
||||
<span class="text-gray-400 text-sm">远程地址</span>
|
||||
<span class="text-gray-400 text-sm">1 端点</span>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作区 -->
|
||||
<div class="flex items-center space-x-4">
|
||||
<button class="flex items-center space-x-1 border border-blue-500 bg-[#0a0a0a] text-white px-3 py-1 rounded text-sm hover:bg-blue-900/20 transition">
|
||||
<span>×</span>
|
||||
<span>断开</span>
|
||||
</button>
|
||||
<button class="text-gray-400 hover:text-white transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-gray-400 hover:text-white transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="text-gray-400 hover:text-white transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex items-center ml-4">
|
||||
<span class="text-gray-400 mr-2 text-sm">计划任务 已启用</span>
|
||||
<button class="w-10 h-5 bg-green-500 rounded-full relative">
|
||||
<span class="absolute right-0.5 top-0.5 w-4 h-4 bg-white rounded-full"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主体内容区 -->
|
||||
<main class="px-6 py-5">
|
||||
<!-- 标题与操作栏 -->
|
||||
<div class="flex justify-between items-start mb-5">
|
||||
<!-- 标题+标签页 -->
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-white mb-3">计划任务</h1>
|
||||
<div class="flex items-center space-x-6">
|
||||
<button class="text-blue-400 border-b-2 border-blue-500 px-1 py-2 font-medium text-sm -mb-[1px]">进行中 3</button>
|
||||
<button class="text-gray-400 hover:text-white px-1 py-2 text-sm transition">已完成 0</button>
|
||||
<button class="text-gray-400 hover:text-white px-1 py-2 text-sm transition">全部 3</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作+搜索区 -->
|
||||
<div class="flex flex-col items-end space-y-2">
|
||||
<div class="flex items-center space-x-3">
|
||||
<button class="flex items-center space-x-1 text-gray-400 hover:text-white text-sm transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<span>刷新</span>
|
||||
</button>
|
||||
<button class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded text-sm flex items-center space-x-1 transition">
|
||||
<span>+</span>
|
||||
<span>新建任务</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<svg class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索任务..."
|
||||
class="w-full bg-[#0a0a0a] border border-gray-700 rounded-lg py-2 pl-10 pr-4 text-sm text-gray-200 focus:outline-none focus:border-gray-600"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务列表 -->
|
||||
<div class="space-y-3">
|
||||
<!-- 任务1:活人感心跳 -->
|
||||
<div class="bg-[#1a1a1a] rounded-lg p-4 hover:bg-[#1e1e1e] transition">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
||||
<span class="text-white font-medium">活人感心跳</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">系统任务</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">Agent 任务</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">间隔重复</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<button class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-3 py-1 rounded transition">暂停</button>
|
||||
<button class="text-gray-400 hover:text-white p-1 transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center flex-wrap gap-x-8 gap-y-1 text-xs text-gray-400 mb-2">
|
||||
<span>状态: 已调度</span>
|
||||
<span>触发方式: 间隔重复 30 分钟</span>
|
||||
<span>下次执行: 2026/03/10 16:26</span>
|
||||
<span>上次执行: 2026/03/10 15:56</span>
|
||||
<span>通知通道: -</span>
|
||||
<span>执行次数: 13</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300">
|
||||
检查是否需要发送主动消息 (问候/提醒/跟进)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务2:记忆整理 -->
|
||||
<div class="bg-[#1a1a1a] rounded-lg p-4 hover:bg-[#1e1e1e] transition">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
||||
<span class="text-white font-medium">记忆整理</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">系统任务</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">Agent 任务</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">间隔重复</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<button class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-3 py-1 rounded transition">暂停</button>
|
||||
<button class="text-gray-400 hover:text-white p-1 transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center flex-wrap gap-x-8 gap-y-1 text-xs text-gray-400 mb-2">
|
||||
<span>状态: 已调度</span>
|
||||
<span>触发方式: 间隔重复 3 小时</span>
|
||||
<span>下次执行: 2026/03/10 18:35</span>
|
||||
<span>上次执行: 2026/03/10 15:35</span>
|
||||
<span>通知通道: -</span>
|
||||
<span>执行次数: 2</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300">
|
||||
执行记忆整理: 整理对话历史, 提取精华记忆, 刷新 MEMORY.md
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务3:系统自检 -->
|
||||
<div class="bg-[#1a1a1a] rounded-lg p-4 hover:bg-[#1e1e1e] transition">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
||||
<span class="text-white font-medium">系统自检</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">系统任务</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">Agent 任务</span>
|
||||
<span class="bg-gray-700 text-gray-300 text-xs px-2 py-0.5 rounded-full">每天</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1">
|
||||
<button class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-xs px-3 py-1 rounded transition">暂停</button>
|
||||
<button class="text-gray-400 hover:text-white p-1 transition">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center flex-wrap gap-x-8 gap-y-1 text-xs text-gray-400 mb-2">
|
||||
<span>状态: 已调度</span>
|
||||
<span>触发方式: 每天 04:00</span>
|
||||
<span>下次执行: 2026/03/11 04:00</span>
|
||||
<span>上次执行: 从未</span>
|
||||
<span>通知通道: -</span>
|
||||
<span>执行次数: 0</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-300">
|
||||
执行系统自检: 分析 ERROR 日志, 尝试修复工具问题, 生成报告
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 300 KiB |
|
Before Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 121 KiB |
BIN
screenshots/计划任务.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
screenshots/记忆整理.png
Normal file
|
After Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 38 KiB |
@@ -51,11 +51,13 @@ const group2 = computed(() => [
|
||||
{ name: 'Knowledge', icon: 'fa-brain', path: '/knowledge', badge: knowledgeCount.value },
|
||||
])
|
||||
|
||||
// 第3组: Skills, Tools, Script
|
||||
// 第3组: Skills, Tools, Script, Plan, Memory
|
||||
const group3 = computed(() => [
|
||||
{ name: 'Skills', icon: 'fa-wand-magic-sparkles', badge: 21, path: '/mcp' },
|
||||
{ name: 'Tools', icon: 'fa-tools', badge: 13, path: '/model-apis' },
|
||||
{ name: 'Script', icon: 'fa-code', path: '/script' },
|
||||
{ name: 'Plan', icon: 'fa-clock', path: '/plan' },
|
||||
{ name: 'Memory', icon: 'fa-brain', path: '/memory' },
|
||||
])
|
||||
|
||||
// 第4组: Dashboard, Account, Settings
|
||||
|
||||
@@ -8,9 +8,12 @@ import Skill from '@/views/Skill.vue'
|
||||
import ModelAPIs from '@/views/ModelAPIs.vue'
|
||||
import Database from '@/views/Database.vue'
|
||||
import Script from '@/views/Script.vue'
|
||||
import Plan from '@/views/Plan.vue'
|
||||
import Memory from '@/views/Memory.vue'
|
||||
import Knowledge from '@/views/Knowledge.vue'
|
||||
import Settings from '@/views/Settings.vue'
|
||||
import Account from '@/views/Account.vue'
|
||||
import Logs from '@/views/Logs.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
@@ -60,6 +63,16 @@ const router = createRouter({
|
||||
name: 'script',
|
||||
component: Script
|
||||
},
|
||||
{
|
||||
path: '/plan',
|
||||
name: 'plan',
|
||||
component: Plan
|
||||
},
|
||||
{
|
||||
path: '/memory',
|
||||
name: 'memory',
|
||||
component: Memory
|
||||
},
|
||||
{
|
||||
path: '/knowledge',
|
||||
name: 'knowledge',
|
||||
@@ -74,6 +87,11 @@ const router = createRouter({
|
||||
path: '/account',
|
||||
name: 'account',
|
||||
component: Account
|
||||
},
|
||||
{
|
||||
path: '/logs',
|
||||
name: 'logs',
|
||||
component: Logs
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -1,15 +1,484 @@
|
||||
<script setup lang="ts">
|
||||
// Account 页面 - 占位
|
||||
import { ref, computed } from 'vue'
|
||||
import './database/database.css'
|
||||
|
||||
// 菜单类型
|
||||
type MenuKey = 'users' | 'roles' | 'permissions'
|
||||
|
||||
// 当前选中的菜单
|
||||
const activeMenu = ref<MenuKey>('users')
|
||||
|
||||
// 菜单列表
|
||||
const menuItems = [
|
||||
{ key: 'users', label: 'Users', icon: 'fa-users' },
|
||||
{ key: 'roles', label: 'Roles', icon: 'fa-user-shield' },
|
||||
{ key: 'permissions', label: 'Permissions', icon: 'fa-lock' },
|
||||
]
|
||||
|
||||
// 用户数据
|
||||
interface User {
|
||||
id: number
|
||||
name: string
|
||||
email: string
|
||||
role: string
|
||||
status: 'active' | 'inactive'
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
const users = ref<User[]>([
|
||||
{ id: 1, name: 'Alex Smith', email: 'alex@example.com', role: 'Admin', status: 'active', createdAt: '2025-01-15' },
|
||||
{ id: 2, name: 'John Doe', email: 'john@example.com', role: 'Developer', status: 'active', createdAt: '2025-02-20' },
|
||||
{ id: 3, name: 'Jane Wilson', email: 'jane@example.com', role: 'Viewer', status: 'active', createdAt: '2025-03-05' },
|
||||
{ id: 4, name: 'Mike Brown', email: 'mike@example.com', role: 'Developer', status: 'inactive', createdAt: '2025-03-10' },
|
||||
{ id: 5, name: 'Sarah Davis', email: 'sarah@example.com', role: 'Admin', status: 'active', createdAt: '2025-03-15' },
|
||||
])
|
||||
|
||||
// 角色数据
|
||||
interface Role {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
userCount: number
|
||||
permissions: string[]
|
||||
}
|
||||
|
||||
const roles = ref<Role[]>([
|
||||
{ id: 1, name: 'Admin', description: 'Full access to all features', userCount: 2, permissions: ['all'] },
|
||||
{ id: 2, name: 'Developer', description: 'Access to development tools', userCount: 2, permissions: ['read', 'write', 'execute'] },
|
||||
{ id: 3, name: 'Viewer', description: 'Read-only access', userCount: 1, permissions: ['read'] },
|
||||
{ id: 4, name: 'Manager', description: 'Manage users and resources', userCount: 0, permissions: ['read', 'write', 'manage'] },
|
||||
])
|
||||
|
||||
// 权限数据
|
||||
interface Permission {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
category: string
|
||||
}
|
||||
|
||||
const permissions = ref<Permission[]>([
|
||||
{ id: 'read', name: 'Read', description: 'View resources', category: 'General' },
|
||||
{ id: 'write', name: 'Write', description: 'Create and update resources', category: 'General' },
|
||||
{ id: 'delete', name: 'Delete', description: 'Remove resources', category: 'General' },
|
||||
{ id: 'execute', name: 'Execute', description: 'Run scripts and tools', category: 'Tools' },
|
||||
{ id: 'manage', name: 'Manage', description: 'Manage users and settings', category: 'Admin' },
|
||||
{ id: 'all', name: 'All Access', description: 'Full system access', category: 'Admin' },
|
||||
])
|
||||
|
||||
// 搜索和筛选
|
||||
const searchQuery = ref('')
|
||||
const filterRole = ref('')
|
||||
|
||||
// 编辑状态
|
||||
const isEditingUser = ref(false)
|
||||
const editingUser = ref<User | null>(null)
|
||||
const isEditingRole = ref(false)
|
||||
const editingRole = ref<Role | null>(null)
|
||||
const isCreatingRole = ref(false)
|
||||
const newRole = ref({ name: '', description: '' })
|
||||
|
||||
// 过滤后的用户
|
||||
const filteredUsers = computed(() => {
|
||||
return users.value.filter(user => {
|
||||
const matchSearch = user.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
const matchRole = filterRole.value === '' || user.role === filterRole.value
|
||||
return matchSearch && matchRole
|
||||
})
|
||||
})
|
||||
|
||||
// 获取用户角色选项
|
||||
const roleOptions = computed(() => {
|
||||
return [...new Set(users.value.map(u => u.role))]
|
||||
})
|
||||
|
||||
// 编辑用户
|
||||
const openEditUser = (user: User) => {
|
||||
editingUser.value = { ...user }
|
||||
isEditingUser.value = true
|
||||
}
|
||||
|
||||
const saveUser = () => {
|
||||
if (editingUser.value) {
|
||||
const index = users.value.findIndex(u => u.id === editingUser.value!.id)
|
||||
if (index !== -1) {
|
||||
users.value[index] = { ...editingUser.value }
|
||||
}
|
||||
}
|
||||
isEditingUser.value = false
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
const deleteUser = (id: number) => {
|
||||
users.value = users.value.filter(u => u.id !== id)
|
||||
}
|
||||
|
||||
// 切换用户状态
|
||||
const toggleUserStatus = (user: User) => {
|
||||
user.status = user.status === 'active' ? 'inactive' : 'active'
|
||||
}
|
||||
|
||||
// 编辑角色
|
||||
const openEditRole = (role: Role) => {
|
||||
editingRole.value = { ...role }
|
||||
isEditingRole.value = true
|
||||
}
|
||||
|
||||
const saveRole = () => {
|
||||
if (editingRole.value) {
|
||||
const index = roles.value.findIndex(r => r.id === editingRole.value!.id)
|
||||
if (index !== -1) {
|
||||
roles.value[index] = { ...editingRole.value }
|
||||
}
|
||||
}
|
||||
isEditingRole.value = false
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
const deleteRole = (id: number) => {
|
||||
roles.value = roles.value.filter(r => r.id !== id)
|
||||
}
|
||||
|
||||
// 创建角色
|
||||
const openCreateRole = () => {
|
||||
newRole.value = { name: '', description: '' }
|
||||
isCreatingRole.value = true
|
||||
}
|
||||
|
||||
const saveNewRole = () => {
|
||||
const newId = Math.max(...roles.value.map(r => r.id)) + 1
|
||||
roles.value.push({
|
||||
id: newId,
|
||||
name: newRole.value.name || 'New Role',
|
||||
description: newRole.value.description,
|
||||
userCount: 0,
|
||||
permissions: [],
|
||||
})
|
||||
isCreatingRole.value = false
|
||||
}
|
||||
|
||||
// 状态样式
|
||||
const statusClass = (status: string) => {
|
||||
return status === 'active' ? 'bg-primary-success' : 'bg-gray-500'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6 min-h-screen">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<i class="fa-solid fa-user text-gray-400"></i>
|
||||
<i class="fa-solid fa-user-shield text-orange-500"></i>
|
||||
<span class="font-medium">Account</span>
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
Account management coming soon...
|
||||
|
||||
<div class="flex gap-6">
|
||||
<!-- 左侧菜单 -->
|
||||
<nav class="w-48 flex-shrink-0">
|
||||
<ul class="space-y-1">
|
||||
<li
|
||||
v-for="item in menuItems"
|
||||
:key="item.key"
|
||||
@click="activeMenu = item.key as MenuKey"
|
||||
class="px-4 py-3 rounded-lg cursor-pointer transition-colors flex items-center gap-3"
|
||||
:class="activeMenu === item.key
|
||||
? 'bg-orange-500/10 text-orange-400'
|
||||
: 'text-gray-400 hover:bg-dark-600 hover:text-white'"
|
||||
>
|
||||
<i :class="['fa-solid', item.icon]"></i>
|
||||
<span>{{ item.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<div class="flex-1">
|
||||
<!-- Users -->
|
||||
<div v-if="activeMenu === 'users'" class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold">Users</h2>
|
||||
<button class="bg-gradient-to-r from-primary-orange to-red-500 hover:from-orange-500 hover:to-red-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-all">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
Add User
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-1 relative">
|
||||
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search users..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg py-2 pl-10 pr-4 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
<el-select v-model="filterRole" placeholder="All Roles" class="w-40" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option label="All Roles" value="" />
|
||||
<el-option v-for="role in roleOptions" :key="role" :label="role" :value="role" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">User</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Role</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Created</th>
|
||||
<th class="text-right px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in filteredUsers" :key="user.id" class="border-t border-dark-600 hover:bg-dark-600/50">
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-orange-500 to-red-500 flex items-center justify-center text-white text-sm font-medium">
|
||||
{{ user.name.charAt(0) }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium">{{ user.name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ user.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="bg-dark-500 px-2 py-1 rounded text-sm">{{ user.role }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="statusClass(user.status)"></span>
|
||||
<span class="capitalize text-sm">{{ user.status }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-400 text-sm">{{ user.createdAt }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
@click="toggleUserStatus(user)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
:title="user.status === 'active' ? 'Deactivate' : 'Activate'"
|
||||
>
|
||||
<i :class="['fa-solid', user.status === 'active' ? 'fa-ban' : 'fa-check', 'text-gray-400 hover:text-white']"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="openEditUser(user)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
title="Edit"
|
||||
>
|
||||
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="deleteUser(user.id)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
title="Delete"
|
||||
>
|
||||
<i class="fa-solid fa-trash text-gray-400 hover:text-red-400"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-if="filteredUsers.length === 0" class="py-12 text-center text-gray-500">
|
||||
<i class="fa-solid fa-users text-4xl mb-3"></i>
|
||||
<p>No users found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Roles -->
|
||||
<div v-if="activeMenu === 'roles'" class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold">Roles</h2>
|
||||
<button @click="openCreateRole" class="bg-gradient-to-r from-primary-orange to-red-500 hover:from-orange-500 hover:to-red-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-all">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
Add Role
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 角色列表 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div v-for="role in roles" :key="role.id" class="bg-dark-700 rounded-xl p-5 border border-dark-600">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
<h3 class="font-semibold text-lg">{{ role.name }}</h3>
|
||||
<p class="text-sm text-gray-400">{{ role.description }}</p>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button @click="openEditRole(role)" class="p-2 rounded-lg hover:bg-dark-600 transition-colors">
|
||||
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
<button @click="deleteRole(role.id)" class="p-2 rounded-lg hover:bg-dark-600 transition-colors">
|
||||
<i class="fa-solid fa-trash text-gray-400 hover:text-red-400"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span v-for="perm in role.permissions.slice(0, 3)" :key="perm" class="px-2 py-0.5 bg-dark-600 rounded text-xs text-gray-400">
|
||||
{{ perm }}
|
||||
</span>
|
||||
<span v-if="role.permissions.length > 3" class="px-2 py-0.5 bg-dark-600 rounded text-xs text-gray-400">
|
||||
+{{ role.permissions.length - 3 }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-sm text-gray-400">{{ role.userCount }} users</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permissions -->
|
||||
<div v-if="activeMenu === 'permissions'" class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-xl font-semibold">Permissions</h2>
|
||||
</div>
|
||||
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Permission</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Description</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Category</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="perm in permissions" :key="perm.id" class="border-t border-dark-600 hover:bg-dark-600/50">
|
||||
<td class="px-5 py-4">
|
||||
<span class="font-medium">{{ perm.name }}</span>
|
||||
<span class="text-gray-500 ml-2">({{ perm.id }})</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-400">{{ perm.description }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="px-2 py-1 bg-dark-500 rounded text-sm">{{ perm.category }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑用户弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="isEditingUser" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl">
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">Edit User</h3>
|
||||
<button @click="isEditingUser = false" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Name</label>
|
||||
<input
|
||||
v-model="editingUser!.name"
|
||||
type="text"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Email</label>
|
||||
<input
|
||||
v-model="editingUser!.email"
|
||||
type="email"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Role</label>
|
||||
<el-select v-model="editingUser!.role" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option v-for="role in roleOptions" :key="role" :label="role" :value="role" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Status</label>
|
||||
<el-select v-model="editingUser!.status" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option label="Active" value="active" />
|
||||
<el-option label="Inactive" value="inactive" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||
<button
|
||||
@click="isEditingUser = false"
|
||||
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveUser"
|
||||
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- 创建角色弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="isCreatingRole" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl">
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">Create Role</h3>
|
||||
<button @click="isCreatingRole = false" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Role Name</label>
|
||||
<input
|
||||
v-model="newRole.name"
|
||||
type="text"
|
||||
placeholder="Enter role name..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||||
<textarea
|
||||
v-model="newRole.description"
|
||||
rows="3"
|
||||
placeholder="Describe this role..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||
<button
|
||||
@click="isCreatingRole = false"
|
||||
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveNewRole"
|
||||
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all"
|
||||
>
|
||||
Create Role
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -70,7 +70,7 @@ const deleteAgent = (id: number) => {
|
||||
<!-- 顶部导航 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-robot text-gray-400"></i>
|
||||
<i class="fa-solid fa-robot text-orange-500"></i>
|
||||
<span class="font-medium">Agents</span>
|
||||
</div>
|
||||
<button class="btn-primary">
|
||||
|
||||
@@ -19,6 +19,14 @@ interface Agent {
|
||||
status: 'online' | 'offline'
|
||||
}
|
||||
|
||||
interface ChatSession {
|
||||
id: number
|
||||
title: string
|
||||
agentId: number
|
||||
lastMessage: string
|
||||
timestamp: Date
|
||||
}
|
||||
|
||||
// AI 助手配置
|
||||
const chatAgents = ref<Agent[]>([
|
||||
{ id: 1, name: 'Claude', avatar: '🧠', description: 'Anthropic AI', accentColor: '#f97316', gradient: 'from-orange-500/20 to-amber-500/20', status: 'online' },
|
||||
@@ -32,13 +40,22 @@ const chatAgents = ref<Agent[]>([
|
||||
|
||||
// 当前选中的助手
|
||||
const selectedAgent = ref<Agent | null>(chatAgents.value[0])
|
||||
const sidebarCollapsed = ref(false)
|
||||
|
||||
// 聊天消息
|
||||
const messages = ref<ChatMessage[]>([
|
||||
{ id: 1, role: 'assistant', content: '你好!我是 Claude,你的 AI 助手。有什么我可以帮助你的吗?', timestamp: new Date() },
|
||||
])
|
||||
|
||||
// 模拟历史对话列表
|
||||
const chatSessions = ref<ChatSession[]>([
|
||||
{ id: 1, title: '关于 Python 学习的讨论', agentId: 1, lastMessage: '谢谢你!', timestamp: new Date(Date.now() - 3600000) },
|
||||
{ id: 2, title: '代码调试帮助', agentId: 1, lastMessage: '让我看看这个问题...', timestamp: new Date(Date.now() - 7200000) },
|
||||
{ id: 3, title: '数据分析咨询', agentId: 4, lastMessage: 'DeepSeek: 好的', timestamp: new Date(Date.now() - 86400000) },
|
||||
])
|
||||
|
||||
// 侧边栏展开/收起状态
|
||||
const sidebarCollapsed = ref(false)
|
||||
|
||||
// 输入内容
|
||||
const inputMessage = ref('')
|
||||
const isLoading = ref(false)
|
||||
@@ -118,6 +135,17 @@ const selectAgent = (agent: Agent) => {
|
||||
]
|
||||
}
|
||||
|
||||
// 选择历史对话
|
||||
const selectSession = (session: ChatSession) => {
|
||||
const agent = chatAgents.value.find(a => a.id === session.agentId)
|
||||
if (agent) {
|
||||
selectedAgent.value = agent
|
||||
}
|
||||
messages.value = [
|
||||
{ id: 1, role: 'assistant', content: `已加载会话:${session.title}`, timestamp: new Date() }
|
||||
]
|
||||
}
|
||||
|
||||
// 新建聊天
|
||||
const newChat = () => {
|
||||
messages.value = [
|
||||
@@ -130,6 +158,19 @@ const formatTime = (date: Date) => {
|
||||
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
// 格式化相对时间
|
||||
const formatRelativeTime = (date: Date) => {
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const hours = Math.floor(diff / 3600000)
|
||||
const days = Math.floor(diff / 86400000)
|
||||
|
||||
if (hours < 1) return '刚刚'
|
||||
if (hours < 24) return `${hours}小时前`
|
||||
if (days < 7) return `${days}天前`
|
||||
return date.toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
// 回车发送
|
||||
const handleKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
@@ -145,18 +186,15 @@ const autoResize = (e: Event) => {
|
||||
target.style.height = Math.min(target.scrollHeight, 160) + 'px'
|
||||
}
|
||||
|
||||
// 切换侧边栏
|
||||
// 折叠侧边栏
|
||||
const toggleSidebar = () => {
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value
|
||||
setTimeout(() => {
|
||||
scrollToBottom()
|
||||
}, 350)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@@ -164,27 +202,27 @@ const toggleSidebar = () => {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
@keyframes messageSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
transform: translateY(16px) scale(0.96);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.message-enter {
|
||||
animation: messageSlideIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
animation: messageSlideIn 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
@@ -195,53 +233,53 @@ const toggleSidebar = () => {
|
||||
.cursor-blink {
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-glow {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(249, 115, 22, 0.4); }
|
||||
50% { box-shadow: 0 0 20px 4px rgba(249, 115, 22, 0.2); }
|
||||
}
|
||||
|
||||
.agent-glow {
|
||||
animation: pulse-glow 2s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="h-screen flex bg-[#0a0a0f]">
|
||||
<div class="h-screen flex bg-[#09090b]">
|
||||
<!-- 主聊天区域 -->
|
||||
<div class="flex-1 flex flex-col bg-[#0a0a0f]">
|
||||
<div class="flex-1 flex flex-col bg-[#09090b]">
|
||||
<!-- 顶部栏 -->
|
||||
<div class="h-14 px-6 flex items-center justify-between border-b border-white/5 bg-[#0d0d12]/50 backdrop-blur-sm">
|
||||
<div class="h-16 px-4 flex items-center justify-between border-b border-white/[0.06] bg-[#0c0c0f]/80 backdrop-blur-xl">
|
||||
<!-- 左侧:当前AI信息 -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div v-if="selectedAgent" class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-8 h-8 rounded-lg flex items-center justify-center text-lg shadow-lg"
|
||||
:style="{ backgroundColor: selectedAgent.accentColor + '20', color: selectedAgent.accentColor }"
|
||||
class="w-9 h-9 rounded-xl flex items-center justify-center text-lg shadow-lg"
|
||||
:style="{ backgroundColor: selectedAgent.accentColor + '15', color: selectedAgent.accentColor }"
|
||||
>
|
||||
{{ selectedAgent.avatar }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-medium text-white">{{ selectedAgent?.name || 'Chat' }}</div>
|
||||
<div class="text-sm font-semibold text-white tracking-wide">{{ selectedAgent?.name || 'Chat' }}</div>
|
||||
<div class="text-[11px] flex items-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
||||
<span class="text-white/40">Online</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右上角操作 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="p-2 rounded-lg hover:bg-white/5 text-white/40 hover:text-white transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="p-2 rounded-lg hover:bg-white/5 text-white/40 hover:text-white transition-colors">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 展开侧边栏按钮 -->
|
||||
<!-- 中间:空白 -->
|
||||
<div class="flex-1"></div>
|
||||
|
||||
<!-- 右侧:折叠按钮 -->
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
v-if="sidebarCollapsed"
|
||||
@click="toggleSidebar"
|
||||
class="p-2 rounded-lg hover:bg-white/5 text-white/40 hover:text-white transition-colors"
|
||||
title="Show AI assistants"
|
||||
class="p-2.5 rounded-xl hover:bg-white/[0.06] text-white/35 hover:text-white/80 transition-all duration-200"
|
||||
:title="sidebarCollapsed ? '展开侧边栏' : '收起侧边栏'"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="w-[18px] h-[18px] transition-transform duration-300" :class="sidebarCollapsed ? '' : 'rotate-180'" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4 6h16M4 12h16M4 18h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
@@ -249,20 +287,20 @@ const toggleSidebar = () => {
|
||||
</div>
|
||||
|
||||
<!-- 消息区域 -->
|
||||
<div ref="messagesContainer" class="flex-1 overflow-y-auto px-6 py-6">
|
||||
<div class="max-w-3xl mx-auto space-y-6">
|
||||
<div ref="messagesContainer" class="flex-1 overflow-y-auto py-4">
|
||||
<div class="px-6">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:key="message.id"
|
||||
class="message-enter flex gap-4"
|
||||
class="message-enter flex items-start mb-4"
|
||||
:class="message.role === 'user' ? 'flex-row-reverse' : ''"
|
||||
>
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
class="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 shadow-lg"
|
||||
:class="message.role === 'user' ? 'bg-gradient-to-br from-emerald-500 to-teal-600' : ''"
|
||||
class="w-9 h-9 rounded-full flex-shrink-0 flex items-center justify-center mx-3 mt-1"
|
||||
:class="message.role === 'user' ? 'bg-gradient-to-br from-orange-500 to-amber-600' : ''"
|
||||
:style="message.role === 'assistant' && selectedAgent ? {
|
||||
backgroundColor: selectedAgent.accentColor + '20',
|
||||
backgroundColor: selectedAgent.accentColor + '25',
|
||||
color: selectedAgent.accentColor
|
||||
} : {}"
|
||||
>
|
||||
@@ -270,28 +308,24 @@ const toggleSidebar = () => {
|
||||
<span v-else class="text-lg">{{ selectedAgent?.avatar || '🧠' }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 消息内容 -->
|
||||
<div
|
||||
class="max-w-[75%] rounded-2xl px-4 py-3"
|
||||
:class="message.role === 'user' ? 'bg-[#1e1e28] text-white' : 'bg-transparent'"
|
||||
>
|
||||
<div class="text-sm leading-relaxed whitespace-pre-wrap text-white/90">{{ message.content }}
|
||||
<span v-if="message.isStreaming" class="inline-block w-0.5 h-4 ml-0.5 bg-violet-400 cursor-blink align-middle"></span>
|
||||
<!-- 气泡和时间戳容器 -->
|
||||
<div :class="message.role === 'user' ? 'mr-3 ml-auto' : 'ml-3'">
|
||||
<!-- 消息气泡 -->
|
||||
<div
|
||||
class="px-4 py-2.5 rounded-xl text-[14px] leading-6"
|
||||
:class="message.role === 'user'
|
||||
? 'bg-gradient-to-br from-orange-500 to-orange-600 text-white rounded-tr-sm'
|
||||
: 'bg-[#2a2a35] text-white/90 rounded-tl-sm max-w-[80%]'"
|
||||
>
|
||||
{{ message.content }}
|
||||
<span v-if="message.isStreaming" class="inline-block w-0.5 h-4 ml-0.5 bg-orange-300 cursor-blink align-middle"></span>
|
||||
</div>
|
||||
|
||||
<!-- 消息底部 -->
|
||||
<div class="flex items-center justify-end mt-2 gap-3">
|
||||
<span class="text-[10px] text-white/25">{{ formatTime(message.timestamp) }}</span>
|
||||
<button
|
||||
v-if="message.role === 'assistant' && !message.isStreaming"
|
||||
@click="copyMessage(message.content)"
|
||||
class="text-white/25 hover:text-violet-400 transition-colors"
|
||||
title="Copy"
|
||||
>
|
||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- 时间戳 -->
|
||||
<div
|
||||
class="text-[11px] text-white/30 mt-1"
|
||||
:class="message.role === 'user' ? 'text-right' : ''"
|
||||
>
|
||||
{{ formatTime(message.timestamp) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -299,11 +333,11 @@ const toggleSidebar = () => {
|
||||
</div>
|
||||
|
||||
<!-- 输入区域 -->
|
||||
<div class="p-4 border-t border-white/5 bg-[#0d0d12]/50">
|
||||
<div class="p-5 border-t border-white/[0.06] bg-[#0c0c0f]/60 backdrop-blur-xl">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="relative bg-[#12121a] rounded-2xl border border-white/8 focus-within:border-violet-500/40 focus-within:shadow-lg focus-within:shadow-violet-500/10 transition-all duration-300">
|
||||
<div class="relative bg-[#12121a] rounded-2xl border border-white/[0.08] focus-within:border-orange-500/40 focus-within:shadow-[0_0_30px_rgba(249,115,22,0.08)] transition-all duration-300">
|
||||
<!-- 附件按钮 -->
|
||||
<button class="absolute left-4 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 transition-colors p-1">
|
||||
<button class="absolute left-4 top-1/2 -translate-y-1/2 text-white/25 hover:text-orange-400 transition-colors p-1">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path>
|
||||
</svg>
|
||||
@@ -314,117 +348,111 @@ const toggleSidebar = () => {
|
||||
v-model="inputMessage"
|
||||
@keydown="handleKeydown"
|
||||
@input="autoResize"
|
||||
placeholder="Send a message..."
|
||||
placeholder="发送消息..."
|
||||
rows="1"
|
||||
class="w-full bg-transparent text-white placeholder-white/30 py-3.5 pl-12 pr-24 resize-none focus:outline-none text-sm"
|
||||
class="w-full bg-transparent text-white placeholder-white/25 py-4 pl-12 pr-28 resize-none focus:outline-none text-[15px]"
|
||||
></textarea>
|
||||
|
||||
<!-- 发送按钮 -->
|
||||
<button
|
||||
@click="sendMessage"
|
||||
:disabled="!inputMessage.trim() || isLoading"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-xl bg-violet-500 hover:bg-violet-400 disabled:bg-white/8 disabled:text-white/20 text-white transition-all duration-200 hover:shadow-lg hover:shadow-violet-500/25"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 w-9 h-9 rounded-lg flex items-center justify-center transition-all duration-200 disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
:class="inputMessage.trim() && !isLoading
|
||||
? 'bg-orange-500 hover:bg-orange-400 shadow-lg shadow-orange-500/30 active:scale-90'
|
||||
: 'bg-white/10'"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
|
||||
<svg v-if="!isLoading" class="w-4 h-4 text-white" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
|
||||
</svg>
|
||||
<svg v-else class="w-4 h-4 text-white animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 提示 -->
|
||||
<div class="text-center mt-2.5">
|
||||
<span class="text-[10px] text-white/20">AI can make mistakes. Please verify important information.</span>
|
||||
<div class="text-center mt-3">
|
||||
<span class="text-[10px] text-white/20 tracking-wide">AI 可能会产生错误信息,请核实重要内容</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧边栏 - 可折叠 -->
|
||||
<transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 translate-x-4"
|
||||
enter-to-class="opacity-100 translate-x-0"
|
||||
leave-active-class="transition-all duration-250 ease-in"
|
||||
leave-from-class="opacity-100 translate-x-0"
|
||||
leave-to-class="opacity-0 translate-x-4"
|
||||
<!-- 右侧边栏:AI Hub -->
|
||||
<div
|
||||
class="flex-shrink-0 border-l border-white/[0.06] bg-[#0c0c0f] transition-all duration-300 ease-in-out overflow-hidden"
|
||||
:class="sidebarCollapsed ? 'w-0 opacity-0' : 'w-72 opacity-100'"
|
||||
>
|
||||
<div v-show="!sidebarCollapsed" class="w-72 bg-[#0d0d12] border-l border-white/5 flex flex-col">
|
||||
<!-- Logo -->
|
||||
<div class="p-4 border-b border-white/5">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500 to-indigo-600 flex items-center justify-center shadow-lg shadow-violet-500/25">
|
||||
<span class="text-white text-lg">🤖</span>
|
||||
</div>
|
||||
<span class="text-lg font-semibold text-white tracking-tight">AI Hub</span>
|
||||
</div>
|
||||
<div class="w-72 h-full flex flex-col">
|
||||
<!-- 侧边栏头部 -->
|
||||
<div class="p-4 border-b border-white/[0.06]">
|
||||
<div class="flex items-center gap-2 text-white font-semibold">
|
||||
<svg class="w-5 h-5 text-orange-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
|
||||
</svg>
|
||||
<span>AI Hub</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新建对话按钮 -->
|
||||
<div class="p-3">
|
||||
<button
|
||||
@click="newChat"
|
||||
class="w-full flex items-center gap-2 px-3 py-2.5 bg-orange-500 hover:bg-orange-400 rounded-lg text-white text-sm font-medium transition-all duration-200"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
<span>新建对话</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- AI 助手选择 -->
|
||||
<div class="px-3 pb-3">
|
||||
<div class="text-xs text-white/40 uppercase tracking-wider mb-2 px-1">选择 AI 助手</div>
|
||||
<div class="space-y-1">
|
||||
<button
|
||||
@click="toggleSidebar"
|
||||
class="p-1.5 rounded-lg hover:bg-white/5 text-white/30 hover:text-white/60 transition-colors"
|
||||
title="Hide sidebar"
|
||||
v-for="agent in chatAgents"
|
||||
:key="agent.id"
|
||||
@click="selectAgent(agent)"
|
||||
class="w-full flex items-center gap-2 px-3 py-2 rounded-lg transition-all duration-200"
|
||||
:class="selectedAgent?.id === agent.id
|
||||
? 'bg-orange-500/15 text-orange-400'
|
||||
: 'text-white/60 hover:bg-white/5 hover:text-white'"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
<span class="text-base">{{ agent.avatar }}</span>
|
||||
<span class="text-sm truncate">{{ agent.name }}</span>
|
||||
<span
|
||||
v-if="agent.status === 'online'"
|
||||
class="w-1.5 h-1.5 rounded-full bg-emerald-400 ml-auto"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新建聊天按钮 -->
|
||||
<div class="p-4">
|
||||
<button
|
||||
@click="newChat"
|
||||
class="w-full py-2.5 px-4 bg-[#1a1a24] hover:bg-[#22222e] border border-white/8 hover:border-violet-500/30 rounded-xl text-white/90 text-sm flex items-center justify-center gap-2 transition-all duration-200 hover:shadow-lg hover:shadow-violet-500/10 group"
|
||||
>
|
||||
<svg class="w-4 h-4 text-violet-400 group-hover:rotate-90 transition-transform duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||
</svg>
|
||||
<span class="font-medium">New Chat</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- AI 助手列表 -->
|
||||
<div class="flex-1 overflow-y-auto px-3 py-2">
|
||||
<div class="text-[11px] font-medium text-white/30 uppercase tracking-wider px-3 mb-3">AI Assistants</div>
|
||||
<!-- 历史对话列表 -->
|
||||
<div class="flex-1 overflow-y-auto px-3 pb-3">
|
||||
<div class="text-xs text-white/40 uppercase tracking-wider mb-2 px-1">最近对话</div>
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="agent in chatAgents"
|
||||
:key="agent.id"
|
||||
@click="selectAgent(agent)"
|
||||
class="group px-3 py-2.5 rounded-xl cursor-pointer transition-all duration-200"
|
||||
:class="selectedAgent?.id === agent.id
|
||||
? 'bg-gradient-to-r ' + agent.gradient + ' border-l-2'
|
||||
: 'hover:bg-white/[0.03] border-l-2 border-transparent'"
|
||||
:style="selectedAgent?.id === agent.id ? `border-left-color: ${agent.accentColor}` : ''"
|
||||
<button
|
||||
v-for="session in chatSessions"
|
||||
:key="session.id"
|
||||
@click="selectSession(session)"
|
||||
class="w-full text-left px-3 py-2.5 rounded-lg hover:bg-white/5 transition-all duration-200 group"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-8 h-8 rounded-lg flex items-center justify-center text-lg transition-transform duration-200 group-hover:scale-110"
|
||||
:class="selectedAgent?.id === agent.id ? 'shadow-lg' : ''"
|
||||
:style="{ backgroundColor: agent.accentColor + '20', color: agent.accentColor }"
|
||||
>
|
||||
{{ agent.avatar }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-white/90 truncate">{{ agent.name }}</div>
|
||||
<div class="text-[11px] text-white/40 truncate">{{ agent.description }}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-white/30 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"></path>
|
||||
</svg>
|
||||
<span class="text-sm text-white/70 group-hover:text-white truncate">{{ session.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-white/30 mt-1 pl-6">{{ formatRelativeTime(session.timestamp) }}</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部设置 -->
|
||||
<div class="p-4 border-t border-white/5">
|
||||
<button class="w-full py-2.5 rounded-xl bg-white/[0.02] hover:bg-white/[0.05] text-white/50 hover:text-white/80 text-sm flex items-center justify-center gap-2 transition-all duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
<span>Settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -118,7 +118,7 @@ const topRequestsTab = ref<'general' | 'errors'>('general')
|
||||
<!-- 顶部导航与日期选择区 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-gauge text-gray-400"></i>
|
||||
<i class="fa-solid fa-gauge text-orange-500"></i>
|
||||
<span class="font-medium">Dashboard</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
|
||||
@@ -57,7 +57,7 @@ const {
|
||||
<!-- 顶部导航 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-database text-gray-400"></i>
|
||||
<i class="fa-solid fa-database text-orange-500"></i>
|
||||
<span class="font-medium">Database</span>
|
||||
</div>
|
||||
<button @click="openCreate" class="btn-primary">
|
||||
|
||||
@@ -716,7 +716,7 @@ const deleteDocument = async (docId: string) => {
|
||||
<!-- 顶部导航 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-brain text-gray-400"></i>
|
||||
<i class="fa-solid fa-brain text-orange-500"></i>
|
||||
<span class="font-medium">Knowledge Base</span>
|
||||
</div>
|
||||
<button @click="openCreateDialog" class="btn-primary">
|
||||
|
||||
226
web/src/views/Logs.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import './database/database.css'
|
||||
|
||||
// 日志数据
|
||||
interface Log {
|
||||
id: number
|
||||
level: 'info' | 'warning' | 'error' | 'debug'
|
||||
source: string
|
||||
message: string
|
||||
timestamp: string
|
||||
user?: string
|
||||
}
|
||||
|
||||
const logs = ref<Log[]>([
|
||||
{ id: 1, level: 'info', source: 'System', message: 'User logged in successfully', timestamp: '2025-03-10 14:35:22', user: 'alex@example.com' },
|
||||
{ id: 2, level: 'warning', source: 'API', message: 'Rate limit approaching for API key', timestamp: '2025-03-10 14:32:15', user: 'john@example.com' },
|
||||
{ id: 3, level: 'error', source: 'Database', message: 'Connection timeout to primary database', timestamp: '2025-03-10 14:30:45' },
|
||||
{ id: 4, level: 'info', source: 'Skill', message: 'MCP Server started successfully', timestamp: '2025-03-10 14:28:10' },
|
||||
{ id: 5, level: 'debug', source: 'Auth', message: 'Token refresh initiated', timestamp: '2025-03-10 14:25:33', user: 'jane@example.com' },
|
||||
{ id: 6, level: 'error', source: 'Script', message: 'Failed to execute backup script', timestamp: '2025-03-10 14:20:18' },
|
||||
{ id: 7, level: 'info', source: 'Account', message: 'User role updated', timestamp: '2025-03-10 14:15:42', user: 'admin@example.com' },
|
||||
{ id: 8, level: 'warning', source: 'Memory', message: 'Memory usage exceeds 80% threshold', timestamp: '2025-03-10 14:10:55' },
|
||||
{ id: 9, level: 'info', source: 'Knowledge', message: 'Document indexed successfully', timestamp: '2025-03-10 14:05:30' },
|
||||
{ id: 10, level: 'error', source: 'API', message: 'Invalid API key provided', timestamp: '2025-03-10 14:00:12' },
|
||||
])
|
||||
|
||||
// 搜索和筛选
|
||||
const searchQuery = ref('')
|
||||
const filterLevel = ref('')
|
||||
const filterSource = ref('')
|
||||
|
||||
// 日志级别选项
|
||||
const levelOptions = [
|
||||
{ value: '', label: 'All Levels' },
|
||||
{ value: 'info', label: 'Info' },
|
||||
{ value: 'warning', label: 'Warning' },
|
||||
{ value: 'error', label: 'Error' },
|
||||
{ value: 'debug', label: 'Debug' },
|
||||
]
|
||||
|
||||
// 来源选项
|
||||
const sourceOptions = computed(() => {
|
||||
const sources = [...new Set(logs.value.map(l => l.source))]
|
||||
return [{ value: '', label: 'All Sources' }, ...sources.map(s => ({ value: s, label: s }))]
|
||||
})
|
||||
|
||||
// 过滤后的日志
|
||||
const filteredLogs = computed(() => {
|
||||
return logs.value.filter(log => {
|
||||
const matchSearch = searchQuery.value === '' ||
|
||||
log.message.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
log.source.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
const matchLevel = filterLevel.value === '' || log.level === filterLevel.value
|
||||
const matchSource = filterSource.value === '' || log.source === filterSource.value
|
||||
return matchSearch && matchLevel && matchSource
|
||||
})
|
||||
})
|
||||
|
||||
// 日志级别样式
|
||||
const levelClass = (level: string) => {
|
||||
switch (level) {
|
||||
case 'info': return 'bg-blue-500/20 text-blue-400'
|
||||
case 'warning': return 'bg-yellow-500/20 text-yellow-400'
|
||||
case 'error': return 'bg-red-500/20 text-red-400'
|
||||
case 'debug': return 'bg-gray-500/20 text-gray-400'
|
||||
default: return 'bg-gray-500/20 text-gray-400'
|
||||
}
|
||||
}
|
||||
|
||||
// 日志详情
|
||||
const selectedLog = ref<Log | null>(null)
|
||||
const showLogDetail = ref(false)
|
||||
|
||||
const viewDetail = (log: Log) => {
|
||||
selectedLog.value = log
|
||||
showLogDetail.value = true
|
||||
}
|
||||
|
||||
const closeDetail = () => {
|
||||
showLogDetail.value = false
|
||||
selectedLog.value = null
|
||||
}
|
||||
|
||||
// 清空日志
|
||||
const clearLogs = () => {
|
||||
logs.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6 min-h-screen">
|
||||
<!-- 顶部标题 -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-file-lines text-orange-500"></i>
|
||||
<span class="font-medium">Logs</span>
|
||||
</div>
|
||||
<button @click="clearLogs" class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors flex items-center gap-2">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
Clear Logs
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="flex gap-4 mb-6">
|
||||
<div class="flex-1 relative">
|
||||
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search logs..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg py-2 pl-10 pr-4 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
<el-select v-model="filterLevel" placeholder="All Levels" class="w-40" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option v-for="opt in levelOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
<el-select v-model="filterSource" placeholder="All Sources" class="w-40" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option v-for="opt in sourceOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Level</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Source</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Message</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">User</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Timestamp</th>
|
||||
<th class="text-right px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="log in filteredLogs" :key="log.id" class="border-t border-dark-600 hover:bg-dark-600/50">
|
||||
<td class="px-5 py-4">
|
||||
<span :class="['px-2 py-1 rounded text-xs font-medium', levelClass(log.level)]">
|
||||
{{ log.level.toUpperCase() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-300">{{ log.source }}</td>
|
||||
<td class="px-5 py-4 text-gray-300 max-w-md">
|
||||
<div class="truncate">{{ log.message }}</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-400">{{ log.user || '-' }}</td>
|
||||
<td class="px-5 py-4 text-gray-400 text-sm">{{ log.timestamp }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center justify-end">
|
||||
<button
|
||||
@click="viewDetail(log)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
title="View Details"
|
||||
>
|
||||
<i class="fa-solid fa-eye text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredLogs.length === 0" class="py-12 text-center text-gray-500">
|
||||
<i class="fa-solid fa-file-lines text-4xl mb-3"></i>
|
||||
<p>No logs found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志详情弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="showLogDetail" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click="closeDetail">
|
||||
<div class="bg-dark-700 rounded-2xl w-full max-w-2xl border border-dark-500 shadow-2xl" @click.stop>
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">Log Details</h3>
|
||||
<button @click="closeDetail" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedLog" class="p-5 space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<span :class="['px-3 py-1 rounded text-sm font-medium', levelClass(selectedLog.level)]">
|
||||
{{ selectedLog.level.toUpperCase() }}
|
||||
</span>
|
||||
<span class="text-gray-400">{{ selectedLog.timestamp }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Source</label>
|
||||
<div class="text-white">{{ selectedLog.source }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">User</label>
|
||||
<div class="text-white">{{ selectedLog.user || 'System' }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Message</label>
|
||||
<div class="bg-dark-800 rounded-lg p-4 text-gray-300 font-mono text-sm whitespace-pre-wrap">
|
||||
{{ selectedLog.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Log ID</label>
|
||||
<div class="text-gray-500">#{{ selectedLog.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||
<button
|
||||
@click="closeDetail"
|
||||
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
309
web/src/views/Memory.vue
Normal file
@@ -0,0 +1,309 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import './database/database.css'
|
||||
|
||||
interface MemoryItem {
|
||||
id: number
|
||||
type: 'Experience' | 'Lessons'
|
||||
content: string
|
||||
subject: string
|
||||
attribute: string
|
||||
score: number
|
||||
createdAt: string
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
// 记忆数据
|
||||
const memories = ref<MemoryItem[]>([
|
||||
{
|
||||
id: 1,
|
||||
type: 'Experience',
|
||||
content: '当任务明确要求获取外部信息(搜索最新数据、新闻、财报、行业报告等)时,模型必须主动调用搜索工具而非仅依赖内部知识。应在系统提示中明确要求「必须使用搜索工具获取XX最新数据」,首次迭代即调用搜索工具,采用「搜索→分析→输出」的递进式流程,避免模型陷入纯文字生成的空转循环。',
|
||||
subject: '外部信息获取规范',
|
||||
attribute: '工具调用原则',
|
||||
score: 0.95,
|
||||
createdAt: '3/10 15:35',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'Experience',
|
||||
content: '当任务明确要求获取外部信息(搜索最新数据、新闻、财报、行业报告等)时,模型必须调用搜索工具而非仅依赖内部知识。应在系统提示中明确要求「必须使用搜索工具获取XX最新数据」,并建立工具调用检测机制,确保任务执行路径正确。',
|
||||
subject: '工具调用',
|
||||
attribute: '错误教训',
|
||||
score: 0.95,
|
||||
createdAt: '3/10 12:34',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'Experience',
|
||||
content: '任务执行应采用「一次性完整输出」策略,避免分批次小幅输出导致的迭代空转。对于代码生成、文件写入等任务,应要求模型一次性完成方案设计与工具执行的完整流程,将确认性回复并入上一次响应,将冗余迭代压缩合并,复杂任务控制在2-3次迭代内完成。',
|
||||
subject: '任务执行效率优化',
|
||||
attribute: '迭代策略原则',
|
||||
score: 0.92,
|
||||
createdAt: '3/10 15:35',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'Experience',
|
||||
content: '任务执行过程中应建立工具调用检测机制,监控模型是否按要求调用了必要的工具。若迭代中工具调用数为0,说明执行路径不正确,应触发异常处理或提示,而非继续无效迭代。',
|
||||
subject: '执行路径监控',
|
||||
attribute: '质量控制机制',
|
||||
score: 0.88,
|
||||
createdAt: '3/10 15:35',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'Experience',
|
||||
content: '任务执行应采用「搜索→分析→输出」的递进式流程,避免无效迭代和空转。纯文字生成的重复迭代应压缩或合并,确认性回复应并入上一次响应。简单任务应在1-2次迭代内完成,避免模型陷入重复思考或无意义的自我确认。',
|
||||
subject: '迭代效率',
|
||||
attribute: '错误教训',
|
||||
score: 0.85,
|
||||
createdAt: '3/10 12:34',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: 'Experience',
|
||||
content: '对于代码生成或文件写入类任务,应要求模型一次性完整输出或按模块批量输出,避免分批次的小幅输出导致迭代次数过多、执行效率低下。',
|
||||
subject: '代码生成策略',
|
||||
attribute: '错误教训',
|
||||
score: 0.80,
|
||||
createdAt: '3/10 12:34',
|
||||
selected: false
|
||||
},
|
||||
])
|
||||
|
||||
// 搜索和筛选
|
||||
const searchQuery = ref('')
|
||||
const filterType = ref('All Types')
|
||||
|
||||
// 统计数据
|
||||
const stats = computed(() => ({
|
||||
total: memories.value.length,
|
||||
avgScore: (memories.value.reduce((sum, m) => sum + m.score, 0) / memories.value.length).toFixed(2),
|
||||
experience: memories.value.filter(m => m.type === 'Experience').length,
|
||||
lessons: memories.value.filter(m => m.type === 'Lessons').length,
|
||||
}))
|
||||
|
||||
// 过滤后的记忆
|
||||
const filteredMemories = computed(() => {
|
||||
return memories.value.filter(m => {
|
||||
const matchSearch = searchQuery.value === '' ||
|
||||
m.content.includes(searchQuery.value) ||
|
||||
m.subject.includes(searchQuery.value) ||
|
||||
m.attribute.includes(searchQuery.value)
|
||||
const matchType = filterType.value === 'All Types' || m.type === filterType.value
|
||||
return matchSearch && matchType
|
||||
})
|
||||
})
|
||||
|
||||
// 切换选中状态
|
||||
const toggleSelect = (id: number) => {
|
||||
const memory = memories.value.find(m => m.id === id)
|
||||
if (memory) {
|
||||
memory.selected = !memory.selected
|
||||
}
|
||||
}
|
||||
|
||||
// 全选
|
||||
const selectAll = ref(false)
|
||||
const toggleSelectAll = () => {
|
||||
selectAll.value = !selectAll.value
|
||||
memories.value.forEach(m => m.selected = selectAll.value)
|
||||
}
|
||||
|
||||
// 删除记忆
|
||||
const deleteMemory = (id: number) => {
|
||||
memories.value = memories.value.filter(m => m.id !== id)
|
||||
}
|
||||
|
||||
// 编辑记忆
|
||||
const editMemory = (id: number) => {
|
||||
console.log('Edit memory:', id)
|
||||
}
|
||||
|
||||
// 刷新
|
||||
const refresh = () => {
|
||||
console.log('Refresh')
|
||||
}
|
||||
|
||||
// LLM 智能审查
|
||||
const llmReview = () => {
|
||||
console.log('LLM Review')
|
||||
}
|
||||
|
||||
// 获取分数颜色
|
||||
const getScoreColor = (score: number) => {
|
||||
if (score >= 0.9) return 'text-emerald-400'
|
||||
if (score >= 0.7) return 'text-amber-400'
|
||||
return 'text-red-400'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#121212] text-gray-200 p-6">
|
||||
<!-- 顶部标题 -->
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<i class="fa-solid fa-brain text-orange-500"></i>
|
||||
<span class="font-medium">Memory</span>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片区域 -->
|
||||
<div class="grid grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-white mb-1">{{ stats.total }}</div>
|
||||
<div class="text-sm text-gray-400">Total Memories</div>
|
||||
</div>
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-white mb-1">{{ stats.avgScore }}</div>
|
||||
<div class="text-sm text-gray-400">Avg Score</div>
|
||||
</div>
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-cyan-400 mb-1">{{ stats.experience }}</div>
|
||||
<div class="text-sm text-gray-400">Experience</div>
|
||||
</div>
|
||||
<div class="bg-[#1e1e1e] rounded-xl p-5 text-center">
|
||||
<div class="text-3xl font-bold text-red-400 mb-1">{{ stats.lessons }}</div>
|
||||
<div class="text-sm text-gray-400">Lessons</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="mb-4">
|
||||
<div class="relative">
|
||||
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search memories..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg py-2 pl-10 pr-4 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 筛选与操作栏 -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<el-select v-model="filterType" placeholder="Select" class="w-40" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option label="All Types" value="All Types" />
|
||||
<el-option label="Experience" value="Experience" />
|
||||
<el-option label="Lessons" value="Lessons" />
|
||||
</el-select>
|
||||
<button class="flex items-center space-x-2 bg-[#1e1e1e] border border-gray-700 rounded-lg py-2 px-4 text-gray-200 hover:bg-[#2a2a2a]">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
<button class="flex items-center space-x-2 bg-orange-500 rounded-lg py-2 px-4 text-white hover:bg-orange-400">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"></path>
|
||||
</svg>
|
||||
<span>LLM Review</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="w-10 px-5 py-3 text-left text-sm font-medium text-gray-400">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="selectAll"
|
||||
@change="toggleSelectAll"
|
||||
class="rounded bg-dark-600 border-dark-500"
|
||||
>
|
||||
</th>
|
||||
<th class="px-5 py-3 text-left text-sm font-medium text-gray-400">Type</th>
|
||||
<th class="px-5 py-3 text-left text-sm font-medium text-gray-400">Content</th>
|
||||
<th class="px-5 py-3 text-left text-sm font-medium text-gray-400">Score</th>
|
||||
<th class="px-5 py-3 text-left text-sm font-medium text-gray-400">Created</th>
|
||||
<th class="px-5 py-3 text-center text-sm font-medium text-gray-400">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="memory in filteredMemories"
|
||||
:key="memory.id"
|
||||
class="table-row"
|
||||
>
|
||||
<td class="px-5 py-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="memory.selected"
|
||||
@change="toggleSelect(memory.id)"
|
||||
class="rounded bg-dark-600 border-dark-500"
|
||||
>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="px-2 py-1 bg-cyan-900/30 text-cyan-400 text-xs rounded-full">{{ memory.type }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm text-gray-300 max-w-xl">
|
||||
<div class="line-clamp-2">{{ memory.content }}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">Subject: {{ memory.subject }} · Attribute: {{ memory.attribute }}</div>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<span :class="['font-medium', getScoreColor(memory.score)]">{{ memory.score }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-sm text-gray-400">{{ memory.createdAt }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<button
|
||||
@click="editMemory(memory.id)"
|
||||
class="btn-icon"
|
||||
>
|
||||
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="deleteMemory(memory.id)"
|
||||
class="btn-icon"
|
||||
>
|
||||
<i class="fa-solid fa-trash text-gray-400 hover:text-red-400"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredMemories.length === 0" class="py-12 text-center text-gray-500">
|
||||
<i class="fa-solid fa-brain text-3xl mb-2"></i>
|
||||
<p>No memories found</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 自定义滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
border-radius: 4px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
/* 行高限制 */
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -110,7 +110,7 @@ const providerIcon = (provider: string) => {
|
||||
<div class="p-6 min-h-screen">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-cube text-gray-400"></i>
|
||||
<i class="fa-solid fa-cube text-orange-500"></i>
|
||||
<span class="font-medium">Model APIs</span>
|
||||
</div>
|
||||
<button class="bg-gradient-to-r from-primary-orange to-red-500 hover:from-orange-500 hover:to-red-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-all">
|
||||
|
||||
292
web/src/views/Plan.vue
Normal file
@@ -0,0 +1,292 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// Mock scheduled tasks data
|
||||
const tasks = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: 'Human-like Heartbeat',
|
||||
status: 'running',
|
||||
triggerType: 'Interval 30 minutes',
|
||||
nextRun: '2026/03/10 16:26',
|
||||
lastRun: '2026/03/10 15:56',
|
||||
notifyChannel: '-',
|
||||
executionCount: 13,
|
||||
description: 'Check if proactive messages need to be sent (greetings/reminders/follow-ups)',
|
||||
tags: ['System Task', 'Agent Task', 'Interval']
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Memory Organization',
|
||||
status: 'running',
|
||||
triggerType: 'Interval 3 hours',
|
||||
nextRun: '2026/03/10 18:35',
|
||||
lastRun: '2026/03/10 15:35',
|
||||
notifyChannel: '-',
|
||||
executionCount: 2,
|
||||
description: 'Execute memory organization: organize chat history, extract key memories, refresh MEMORY.md',
|
||||
tags: ['System Task', 'Agent Task', 'Interval']
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'System Self-Check',
|
||||
status: 'running',
|
||||
triggerType: 'Daily 04:00',
|
||||
nextRun: '2026/03/11 04:00',
|
||||
lastRun: 'Never',
|
||||
notifyChannel: '-',
|
||||
executionCount: 0,
|
||||
description: 'Execute system self-check: analyze ERROR logs, try to fix tool issues, generate report',
|
||||
tags: ['System Task', 'Agent Task', 'Daily']
|
||||
},
|
||||
])
|
||||
|
||||
const activeTab = ref('running') // running, completed, all
|
||||
const searchQuery = ref('')
|
||||
|
||||
const filteredTasks = computed(() => {
|
||||
let result = tasks.value
|
||||
if (activeTab.value === 'running') {
|
||||
result = result.filter(t => t.status === 'running')
|
||||
} else if (activeTab.value === 'completed') {
|
||||
result = result.filter(t => t.status === 'stopped')
|
||||
}
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
result = result.filter(t =>
|
||||
t.name.toLowerCase().includes(query) ||
|
||||
t.description.toLowerCase().includes(query)
|
||||
)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const getTaskCount = (status: string) => {
|
||||
if (status === 'running') return tasks.value.filter(t => t.status === 'running').length
|
||||
if (status === 'completed') return tasks.value.filter(t => t.status === 'stopped').length
|
||||
return tasks.value.length
|
||||
}
|
||||
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'running': return 'bg-green-500'
|
||||
case 'stopped': return 'bg-gray-500'
|
||||
default: return 'bg-gray-500'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6 min-h-screen plan-page">
|
||||
<!-- Header -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-clock text-orange-500"></i>
|
||||
<span class="font-medium text-white">Scheduled Tasks</span>
|
||||
</div>
|
||||
<button class="btn-primary">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
New Task
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
<div class="flex gap-4 mb-6">
|
||||
<div class="flex-1 relative">
|
||||
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search tasks..."
|
||||
class="search-input w-full"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="flex gap-6 mb-4">
|
||||
<button
|
||||
:class="['tab-item', { active: activeTab === 'running' }]"
|
||||
@click="activeTab = 'running'"
|
||||
>
|
||||
Running {{ getTaskCount('running') }}
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-item', { active: activeTab === 'completed' }]"
|
||||
@click="activeTab = 'completed'"
|
||||
>
|
||||
Completed {{ getTaskCount('completed') }}
|
||||
</button>
|
||||
<button
|
||||
:class="['tab-item', { active: activeTab === 'all' }]"
|
||||
@click="activeTab = 'all'"
|
||||
>
|
||||
All {{ getTaskCount('all') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Task List Table -->
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<div v-if="filteredTasks.length === 0" class="py-12 text-center text-gray-400">
|
||||
<i class="fa-solid fa-clock text-2xl mb-2"></i>
|
||||
<p>No tasks found</p>
|
||||
</div>
|
||||
<table v-else class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Task Name</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Trigger</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Next Run</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Executions</th>
|
||||
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="task in filteredTasks" :key="task.id" class="table-row">
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span :class="['w-2 h-2 rounded-full', getStatusClass(task.status)]"></span>
|
||||
<div>
|
||||
<div class="font-medium text-white">{{ task.name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ task.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 mt-2">
|
||||
<span v-for="tag in task.tags" :key="tag" class="task-tag">{{ tag }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center">
|
||||
<span :class="['status-badge', getStatusClass(task.status)]">
|
||||
{{ task.status === 'running' ? 'Running' : 'Stopped' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">
|
||||
{{ task.triggerType }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center text-gray-400 text-sm">
|
||||
{{ task.nextRun }}
|
||||
</td>
|
||||
<td class="px-5 py-4 text-center">
|
||||
<span class="text-primary-cyan">{{ task.executionCount }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<button class="btn-icon" title="Pause">
|
||||
<i class="fa-solid fa-pause text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
<button class="btn-icon" title="Edit">
|
||||
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
<button class="btn-icon" title="Delete">
|
||||
<i class="fa-solid fa-trash text-gray-400 hover:text-red-500"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plan-page {
|
||||
background-color: #0f1419;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #f97316, #ef4444);
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(249, 115, 22, 0.3);
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background-color: #1f2937;
|
||||
border: 1px solid #374151;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px 10px 36px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: #f97316;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #9ca3af;
|
||||
font-size: 14px;
|
||||
padding: 8px 4px;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #f97316;
|
||||
border-bottom-color: #f97316;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
border-top: 1px solid #2a2a3a;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.table-row:hover {
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.task-tag {
|
||||
background-color: #374151;
|
||||
color: #d1d5db;
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,327 @@
|
||||
<script setup lang="ts">
|
||||
// Script 页面 - 占位
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
interface Script {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
description: string
|
||||
status: 'running' | 'stopped'
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// 模拟脚本数据
|
||||
const scripts = ref<Script[]>([
|
||||
{ id: 1, name: 'Data Processing', type: 'Python', description: 'Process and transform data', status: 'running', createdAt: '2025-04-10' },
|
||||
{ id: 2, name: 'Report Generator', type: 'Python', description: 'Generate weekly reports', status: 'stopped', createdAt: '2025-04-08' },
|
||||
{ id: 3, name: 'Backup Script', type: 'Shell', description: 'Database backup automation', status: 'running', createdAt: '2025-04-05' },
|
||||
{ id: 4, name: 'Data Sync', type: 'Python', description: 'Sync data between systems', status: 'stopped', createdAt: '2025-04-12' },
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
const filterStatus = ref('all')
|
||||
const isCreating = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const editingScript = ref<Script | null>(null)
|
||||
|
||||
const newScriptForm = ref({
|
||||
name: '',
|
||||
type: 'Python',
|
||||
description: '',
|
||||
})
|
||||
|
||||
// 过滤后的脚本
|
||||
const filteredScripts = computed(() => {
|
||||
return scripts.value.filter(script => {
|
||||
const matchSearch = script.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||
script.description.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
const matchStatus = filterStatus.value === 'all' || script.status === filterStatus.value
|
||||
return matchSearch && matchStatus
|
||||
})
|
||||
})
|
||||
|
||||
// 状态样式
|
||||
const statusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'running': return 'bg-primary-success'
|
||||
case 'stopped': return 'bg-gray-500'
|
||||
default: return 'bg-gray-500'
|
||||
}
|
||||
}
|
||||
|
||||
// 切换状态
|
||||
const toggleStatus = (script: Script) => {
|
||||
script.status = script.status === 'running' ? 'stopped' : 'running'
|
||||
}
|
||||
|
||||
// 删除脚本
|
||||
const deleteScript = (id: number) => {
|
||||
scripts.value = scripts.value.filter(s => s.id !== id)
|
||||
}
|
||||
|
||||
// 编辑脚本
|
||||
const openEdit = (script: Script) => {
|
||||
editingScript.value = { ...script }
|
||||
isEditing.value = true
|
||||
}
|
||||
|
||||
const saveEdit = () => {
|
||||
if (editingScript.value) {
|
||||
const index = scripts.value.findIndex(s => s.id === editingScript.value!.id)
|
||||
if (index !== -1) {
|
||||
scripts.value[index] = { ...editingScript.value }
|
||||
}
|
||||
}
|
||||
isEditing.value = false
|
||||
}
|
||||
|
||||
const cancelEdit = () => {
|
||||
isEditing.value = false
|
||||
editingScript.value = null
|
||||
}
|
||||
|
||||
// 新建脚本
|
||||
const openCreate = () => {
|
||||
newScriptForm.value = {
|
||||
name: '',
|
||||
type: 'Python',
|
||||
description: '',
|
||||
}
|
||||
isCreating.value = true
|
||||
}
|
||||
|
||||
const closeCreate = () => {
|
||||
isCreating.value = false
|
||||
}
|
||||
|
||||
const saveNewScript = () => {
|
||||
const newId = Math.max(...scripts.value.map(s => s.id), 0) + 1
|
||||
scripts.value.push({
|
||||
id: newId,
|
||||
name: newScriptForm.value.name || 'Untitled Script',
|
||||
type: newScriptForm.value.type,
|
||||
description: newScriptForm.value.description,
|
||||
status: 'stopped',
|
||||
createdAt: new Date().toISOString().split('T')[0],
|
||||
})
|
||||
isCreating.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6 min-h-screen">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<i class="fa-solid fa-code text-gray-400"></i>
|
||||
<span class="font-medium">Script</span>
|
||||
<!-- 顶部导航 -->
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-code text-orange-500"></i>
|
||||
<span class="font-medium">Scripts</span>
|
||||
</div>
|
||||
<button @click="openCreate" class="bg-gradient-to-r from-primary-orange to-red-500 hover:from-orange-500 hover:to-red-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-all">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
New Script
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
Script management coming soon...
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="flex gap-4 mb-6">
|
||||
<div class="flex-1 relative">
|
||||
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="Search scripts..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg py-2 pl-10 pr-4 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
<el-select v-model="filterStatus" placeholder="Select" class="w-40" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option label="All Status" value="all" />
|
||||
<el-option label="Running" value="running" />
|
||||
<el-option label="Stopped" value="stopped" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 脚本列表 -->
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Script Name</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Type</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Created</th>
|
||||
<th class="text-right px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="script in filteredScripts" :key="script.id" class="border-t border-dark-600 hover:bg-dark-600/50 transition-colors">
|
||||
<td class="px-5 py-4">
|
||||
<div class="font-medium">{{ script.name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ script.description }}</div>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="bg-dark-500 px-2 py-1 rounded text-sm">{{ script.type }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-2 h-2 rounded-full" :class="statusClass(script.status)"></span>
|
||||
<span class="capitalize text-sm">{{ script.status }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-400 text-sm">{{ script.createdAt }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button
|
||||
@click="toggleStatus(script)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
:title="script.status === 'running' ? 'Stop' : 'Start'"
|
||||
>
|
||||
<i :class="['fa-solid', script.status === 'running' ? 'fa-stop' : 'fa-play', 'text-gray-400 hover:text-white']"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="openEdit(script)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
title="Edit"
|
||||
>
|
||||
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="deleteScript(script.id)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
title="Delete"
|
||||
>
|
||||
<i class="fa-solid fa-trash text-gray-400 hover:text-red-400"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredScripts.length === 0" class="py-12 text-center text-gray-500">
|
||||
<i class="fa-solid fa-code text-4xl mb-3"></i>
|
||||
<p>No scripts found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新建脚本弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="isCreating" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl">
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">New Script</h3>
|
||||
<button @click="closeCreate" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Script Name</label>
|
||||
<input
|
||||
v-model="newScriptForm.name"
|
||||
type="text"
|
||||
placeholder="Enter script name..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Type</label>
|
||||
<el-select v-model="newScriptForm.type" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option label="Python" value="Python" />
|
||||
<el-option label="Shell" value="Shell" />
|
||||
<el-option label="JavaScript" value="JavaScript" />
|
||||
<el-option label="Go" value="Go" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||||
<textarea
|
||||
v-model="newScriptForm.description"
|
||||
rows="3"
|
||||
placeholder="Describe your script..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||
<button
|
||||
@click="closeCreate"
|
||||
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveNewScript"
|
||||
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all"
|
||||
>
|
||||
Create Script
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- 编辑脚本弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="isEditing" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50">
|
||||
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl">
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">Edit Script</h3>
|
||||
<button @click="cancelEdit" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Script Name</label>
|
||||
<input
|
||||
v-model="editingScript!.name"
|
||||
type="text"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Type</label>
|
||||
<el-select v-model="editingScript!.type" placeholder="Select" class="w-full" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option label="Python" value="Python" />
|
||||
<el-option label="Shell" value="Shell" />
|
||||
<el-option label="JavaScript" value="JavaScript" />
|
||||
<el-option label="Go" value="Go" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||||
<textarea
|
||||
v-model="editingScript!.description"
|
||||
rows="3"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||
<button
|
||||
@click="cancelEdit"
|
||||
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveEdit"
|
||||
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useModelSettings } from './settings/useModelSettings'
|
||||
import FormDialog from '@/components/FormDialog.vue'
|
||||
@@ -47,6 +47,7 @@ const menuItems = [
|
||||
{ key: 'members', label: 'Members', icon: 'fa-users' },
|
||||
{ key: 'notifications', label: 'Notifications', icon: 'fa-bell' },
|
||||
{ key: 'modelSettings', label: 'Model Settings', icon: 'fa-brain' },
|
||||
{ key: 'logs', label: 'Logs', icon: 'fa-file-lines' },
|
||||
]
|
||||
|
||||
// General 设置表单
|
||||
@@ -82,13 +83,92 @@ const saveChanges = () => {
|
||||
const showChangePassword = () => {
|
||||
ElMessage.info('Password change dialog would open here')
|
||||
}
|
||||
|
||||
// ========== Logs 功能 ==========
|
||||
interface Log {
|
||||
id: number
|
||||
level: 'info' | 'warning' | 'error' | 'debug'
|
||||
source: string
|
||||
message: string
|
||||
timestamp: string
|
||||
user?: string
|
||||
}
|
||||
|
||||
const logs = ref<Log[]>([
|
||||
{ id: 1, level: 'info', source: 'System', message: 'User logged in successfully', timestamp: '2025-03-10 14:35:22', user: 'alex@example.com' },
|
||||
{ id: 2, level: 'warning', source: 'API', message: 'Rate limit approaching for API key', timestamp: '2025-03-10 14:32:15', user: 'john@example.com' },
|
||||
{ id: 3, level: 'error', source: 'Database', message: 'Connection timeout to primary database', timestamp: '2025-03-10 14:30:45' },
|
||||
{ id: 4, level: 'info', source: 'Skill', message: 'MCP Server started successfully', timestamp: '2025-03-10 14:28:10' },
|
||||
{ id: 5, level: 'debug', source: 'Auth', message: 'Token refresh initiated', timestamp: '2025-03-10 14:25:33', user: 'jane@example.com' },
|
||||
{ id: 6, level: 'error', source: 'Script', message: 'Failed to execute backup script', timestamp: '2025-03-10 14:20:18' },
|
||||
{ id: 7, level: 'info', source: 'Account', message: 'User role updated', timestamp: '2025-03-10 14:15:42', user: 'admin@example.com' },
|
||||
{ id: 8, level: 'warning', source: 'Memory', message: 'Memory usage exceeds 80% threshold', timestamp: '2025-03-10 14:10:55' },
|
||||
{ id: 9, level: 'info', source: 'Knowledge', message: 'Document indexed successfully', timestamp: '2025-03-10 14:05:30' },
|
||||
{ id: 10, level: 'error', source: 'API', message: 'Invalid API key provided', timestamp: '2025-03-10 14:00:12' },
|
||||
])
|
||||
|
||||
const logSearchQuery = ref('')
|
||||
const logFilterLevel = ref('')
|
||||
const logFilterSource = ref('')
|
||||
|
||||
const logLevelOptions = [
|
||||
{ value: '', label: 'All Levels' },
|
||||
{ value: 'info', label: 'Info' },
|
||||
{ value: 'warning', label: 'Warning' },
|
||||
{ value: 'error', label: 'Error' },
|
||||
{ value: 'debug', label: 'Debug' },
|
||||
]
|
||||
|
||||
const logSourceOptions = computed(() => {
|
||||
const sources = [...new Set(logs.value.map(l => l.source))]
|
||||
return [{ value: '', label: 'All Sources' }, ...sources.map(s => ({ value: s, label: s }))]
|
||||
})
|
||||
|
||||
const filteredLogs = computed(() => {
|
||||
return logs.value.filter(log => {
|
||||
const matchSearch = logSearchQuery.value === '' ||
|
||||
log.message.toLowerCase().includes(logSearchQuery.value.toLowerCase()) ||
|
||||
log.source.toLowerCase().includes(logSearchQuery.value.toLowerCase())
|
||||
const matchLevel = logFilterLevel.value === '' || log.level === logFilterLevel.value
|
||||
const matchSource = logFilterSource.value === '' || log.source === logFilterSource.value
|
||||
return matchSearch && matchLevel && matchSource
|
||||
})
|
||||
})
|
||||
|
||||
const logLevelClass = (level: string) => {
|
||||
switch (level) {
|
||||
case 'info': return 'bg-blue-500/20 text-blue-400'
|
||||
case 'warning': return 'bg-yellow-500/20 text-yellow-400'
|
||||
case 'error': return 'bg-red-500/20 text-red-400'
|
||||
case 'debug': return 'bg-gray-500/20 text-gray-400'
|
||||
default: return 'bg-gray-500/20 text-gray-400'
|
||||
}
|
||||
}
|
||||
|
||||
const selectedLog = ref<Log | null>(null)
|
||||
const showLogDetail = ref(false)
|
||||
|
||||
const viewLogDetail = (log: Log) => {
|
||||
selectedLog.value = log
|
||||
showLogDetail.value = true
|
||||
}
|
||||
|
||||
const closeLogDetail = () => {
|
||||
showLogDetail.value = false
|
||||
selectedLog.value = null
|
||||
}
|
||||
|
||||
const clearLogs = () => {
|
||||
logs.value = []
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<!-- 页面标题 -->
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">Settings</h1>
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<i class="fa-solid fa-gear text-orange-500"></i>
|
||||
<span class="font-medium">Settings</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-container">
|
||||
@@ -432,6 +512,141 @@ const showChangePassword = () => {
|
||||
</template>
|
||||
</FormDialog>
|
||||
</div>
|
||||
|
||||
<!-- Logs 设置 -->
|
||||
<div v-if="activeMenu === 'logs'" class="settings-section">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 class="section-title">Logs</h2>
|
||||
<p class="section-desc">View system logs</p>
|
||||
</div>
|
||||
<button @click="clearLogs" class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors flex items-center gap-2">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
Clear Logs
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和筛选 -->
|
||||
<div class="flex gap-4 mb-6">
|
||||
<div class="flex-1 relative">
|
||||
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
||||
<input
|
||||
v-model="logSearchQuery"
|
||||
type="text"
|
||||
placeholder="Search logs..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg py-2 pl-10 pr-4 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
<el-select v-model="logFilterLevel" placeholder="All Levels" class="w-40" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option v-for="opt in logLevelOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
<el-select v-model="logFilterSource" placeholder="All Sources" class="w-40" size="large" popper-class="dark-select-dropdown">
|
||||
<el-option v-for="opt in logSourceOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<!-- 日志列表 -->
|
||||
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
||||
<table class="w-full">
|
||||
<thead class="bg-dark-600">
|
||||
<tr>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Level</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Source</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Message</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">User</th>
|
||||
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Timestamp</th>
|
||||
<th class="text-right px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="log in filteredLogs" :key="log.id" class="border-t border-dark-600 hover:bg-dark-600/50">
|
||||
<td class="px-5 py-4">
|
||||
<span :class="['px-2 py-1 rounded text-xs font-medium', logLevelClass(log.level)]">
|
||||
{{ log.level.toUpperCase() }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-300">{{ log.source }}</td>
|
||||
<td class="px-5 py-4 text-gray-300 max-w-md">
|
||||
<div class="truncate">{{ log.message }}</div>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-400">{{ log.user || '-' }}</td>
|
||||
<td class="px-5 py-4 text-gray-400 text-sm">{{ log.timestamp }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<div class="flex items-center justify-end">
|
||||
<button
|
||||
@click="viewLogDetail(log)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
title="View Details"
|
||||
>
|
||||
<i class="fa-solid fa-eye text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredLogs.length === 0" class="py-12 text-center text-gray-500">
|
||||
<i class="fa-solid fa-file-lines text-4xl mb-3"></i>
|
||||
<p>No logs found</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日志详情弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="showLogDetail" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click="closeLogDetail">
|
||||
<div class="bg-dark-700 rounded-2xl w-full max-w-2xl border border-dark-500 shadow-2xl" @click.stop>
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">Log Details</h3>
|
||||
<button @click="closeLogDetail" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedLog" class="p-5 space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<span :class="['px-3 py-1 rounded text-sm font-medium', logLevelClass(selectedLog.level)]">
|
||||
{{ selectedLog.level.toUpperCase() }}
|
||||
</span>
|
||||
<span class="text-gray-400">{{ selectedLog.timestamp }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Source</label>
|
||||
<div class="text-white">{{ selectedLog.source }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">User</label>
|
||||
<div class="text-white">{{ selectedLog.user || 'System' }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Message</label>
|
||||
<div class="bg-dark-800 rounded-lg p-4 text-gray-300 font-mono text-sm whitespace-pre-wrap">
|
||||
{{ selectedLog.message }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-400 mb-2">Log ID</label>
|
||||
<div class="text-gray-500">#{{ selectedLog.id }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||
<button
|
||||
@click="closeLogDetail"
|
||||
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -308,7 +308,7 @@ const saveNewSkill = () => {
|
||||
<div class="p-6 min-h-screen">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-wand-magic-sparkles text-gray-400"></i>
|
||||
<i class="fa-solid fa-wand-magic-sparkles text-orange-500"></i>
|
||||
<span class="font-medium">Skills</span>
|
||||
</div>
|
||||
<button @click="openCreate" class="bg-gradient-to-r from-primary-orange to-red-500 hover:from-orange-500 hover:to-red-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-all">
|
||||
|
||||