feat(AgentPopover): 增强待办事项图标样式并添加文件下载功能
- 将简单的文本图标替换为SVG图标,提升视觉体验 - 为文件列表项添加下载按钮功能 - 优化文件区域的样式和布局 - 移除已完成TODO注释和相关代码
This commit is contained in:
parent
10ad84a679
commit
5a66ff80c2
@ -12,6 +12,10 @@
|
||||
- 集成 LangFuse (观望) 添加用户日志与用户反馈模块,可以在 AgentView 中查看信息
|
||||
- 集成 neo4j mcp (或者自己构建工具)
|
||||
- 文档解析部分的 markdown 中的图片替换为内部可访问的链接 (2/4)
|
||||
- 同名文件处理逻辑:遇到同名文件则在上传区域提示,是否删除旧文件
|
||||
- conversation 待修改为异步的版本
|
||||
- DBManager 需要将数据库修改为异步的aiosqlite或者异步mysql,缓存使用Redis存储
|
||||
- agent 状态中的文件区域,新增可以下载
|
||||
|
||||
### Bugs
|
||||
- 部分异常状态下,智能体的模型名称出现重叠[#279](https://github.com/xerrors/Yuxi-Know/issues/279)
|
||||
|
||||
@ -1065,8 +1065,6 @@ async def upload_file(
|
||||
upload_dir = os.path.join(config.save_dir, "database", "uploads")
|
||||
|
||||
basename, ext = os.path.splitext(file.filename)
|
||||
# TODO:
|
||||
# 后续修改为遇到同名文件则在上传区域提示,是否删除旧文件,同时 filename name 也就不用添加 hash 了
|
||||
filename = f"{basename}_{hashstr(basename, 4, with_salt=True, salt='fixed_salt')}{ext}".lower()
|
||||
|
||||
file_path = os.path.join(upload_dir, filename)
|
||||
|
||||
@ -4,8 +4,6 @@ from fastapi import FastAPI
|
||||
|
||||
from server.services import tasker
|
||||
|
||||
# TODO:[已完成]使用lifespan进行统一生命周期管理
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
|
||||
@ -40,7 +40,15 @@
|
||||
class="todo-item"
|
||||
>
|
||||
<span class="todo-status" :class="todo.status">
|
||||
{{ todo.status === 'completed' ? '✓' : todo.status === 'in_progress' ? '⟳' : '○' }}
|
||||
<svg v-if="todo.status === 'completed'" viewBox="0 0 24 24" fill="currentColor" class="icon">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</svg>
|
||||
<svg v-else-if="todo.status === 'in_progress'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="icon spinning">
|
||||
<circle cx="12" cy="12" r="10" stroke-dasharray="31.416" stroke-dashoffset="31.416" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="icon">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
</svg>
|
||||
</span>
|
||||
<span class="todo-text">{{ todo.content }}</span>
|
||||
</div>
|
||||
@ -60,10 +68,19 @@
|
||||
@click="showFileContent(fileItem.path, fileItem)"
|
||||
>
|
||||
<div class="file-info">
|
||||
<div class="file-name">{{ getFileName(fileItem) }}</div>
|
||||
<div class="file-time" v-if="fileItem.modified_at">
|
||||
{{ formatDate(fileItem.modified_at) }}
|
||||
<div class="file-content-wrapper">
|
||||
<div class="file-name">{{ getFileName(fileItem) }}</div>
|
||||
<div class="file-time" v-if="fileItem.modified_at">
|
||||
{{ formatDate(fileItem.modified_at) }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="download-btn"
|
||||
@click.stop="downloadFile(fileItem)"
|
||||
title="下载文件"
|
||||
>
|
||||
<Download :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,6 +109,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Download } from 'lucide-vue-next';
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
@ -211,6 +229,24 @@ const closeModal = () => {
|
||||
currentFilePath.value = '';
|
||||
};
|
||||
|
||||
const downloadFile = (fileItem) => {
|
||||
try {
|
||||
const content = formatContent(fileItem.content);
|
||||
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.href = url;
|
||||
link.download = getFileName(fileItem);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error('下载文件失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const emitRefresh = () => {
|
||||
emit('refresh');
|
||||
};
|
||||
@ -330,19 +366,35 @@ const emitRefresh = () => {
|
||||
|
||||
.todo-status {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
margin-top: 2px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
.icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.spinning {
|
||||
animation: spin 1.5s linear infinite;
|
||||
stroke-dasharray: 31.416;
|
||||
stroke-dashoffset: 0;
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
&.completed {
|
||||
background: var(--color-success-50);
|
||||
color: var(--color-success-700);
|
||||
|
||||
.icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
&.in_progress {
|
||||
@ -356,6 +408,20 @@ const emitRefresh = () => {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dashoffset: 15.708;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.todo-text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
@ -377,7 +443,7 @@ const emitRefresh = () => {
|
||||
|
||||
.file-item {
|
||||
padding: 12px 14px;
|
||||
background: var(--main-5);
|
||||
background: var(--gray-0);
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
@ -395,6 +461,15 @@ const emitRefresh = () => {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-content-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
@ -415,6 +490,29 @@ const emitRefresh = () => {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--gray-600);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--main-600);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--main-400);
|
||||
}
|
||||
}
|
||||
|
||||
.file-content {
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
|
||||
@ -162,7 +162,6 @@
|
||||
<ModelProvidersComponent />
|
||||
</div>
|
||||
|
||||
<!-- TODO 用户管理优化,添加姓名(默认使用用户名配置项) -->
|
||||
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'user') && userStore.isAdmin">
|
||||
<UserManagementComponent />
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user