核心新增 —— ReAct 智能体引擎 (src/agent/): - ReAct 主循环:自主推理→工具调用→观察→迭代,支持流式 SSE 推送、 上下文自动压缩、重复调用死循环检测、手动取消与超时控制 - 7 个内置工具:ADS/arXiv 论文搜索、元数据查询、全文读取、PDF/HTML 下载解析、RAG 向量语义检索、CDS Sesame 天体目标查询 LLM 客户端增强 (src/clients/llm.rs): - 新增流式/非流式 Function Calling (tool calling) 支持 - 原生 reasoning_content 推理链字段 (DeepSeek/QwQ) - 多模态图片+文本对话、TokenUsage 用量统计 搜索服务重构 (src/services/search.rs): - 抽取 ADS/arXiv 搜索为统一 service,跨平台去重、本地状态合并 - API handler 瘦身至薄封装层 下载器增强 (src/services/download.rs): - Obscura 无头浏览器反爬下载通道 (进程内/CLI 双模式) - arXiv/Springer 专用策略、ADS Scan PDF 兜底、CAPTCHA 绕过 解析器增强 (src/services/parser/mod.rs): - Markdown 输出自动附加 YAML 前端元数据头 (标题/作者/来源) 新增 API (src/api/agent.rs): - POST /api/chat/agent — SSE 流式智能体对话 - GET/DELETE /api/chat/sessions — 会话管理 CRUD - POST /api/chat/sessions/:id/stop — 紧急停止 数据库: agent_sessions + agent_messages 表 前端 (dashboard/): - 全新"智能科研"标签页 (ResearchAgentPanel) — 会话列表、ReAct 步骤时间线可视化、SSE 实时流式渲染、Markdown+KaTeX 答案展示 - Reader AI 助手集成 Agent 工作流,含工具调用步骤展示与引用溯源 - 侧边栏新增"智能科研"导航入口
214 lines
10 KiB
TypeScript
214 lines
10 KiB
TypeScript
// dashboard/src/components/layout/Sidebar.tsx
|
|
import { useState } from 'react';
|
|
import { Search, BookOpen, GitFork, Library, RefreshCw, ChevronLeft, Sparkles } from 'lucide-react';
|
|
import type { StandardPaper } from '../../types';
|
|
|
|
interface SidebarProps {
|
|
activeTab: 'search' | 'library' | 'reader' | 'citation' | 'sync' | 'agent';
|
|
setActiveTab: (tab: 'search' | 'library' | 'reader' | 'citation' | 'sync' | 'agent') => void;
|
|
selectedPaper: StandardPaper | null;
|
|
loadCitations: (bibcode: string) => void;
|
|
}
|
|
|
|
export function Sidebar({ activeTab, setActiveTab, selectedPaper, loadCitations }: SidebarProps) {
|
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
|
|
|
const renderLogo = () => (
|
|
<svg width="100%" height="100%" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
|
|
<circle cx="24" cy="24" r="18" stroke="#bae6fd" strokeWidth="1.5" />
|
|
<circle cx="24" cy="24" r="21" stroke="#0284c7" strokeWidth="1.5" strokeDasharray="2 3" />
|
|
<path d="M24 9C24 18 24 18 33 24C24 24 24 24 24 33C24 24 24 24 15 24C24 18 24 18 24 9Z" fill="url(#sidebarStarGrad)" />
|
|
<ellipse cx="24" cy="24" rx="20" ry="7" transform="rotate(-28 24 24)" stroke="#0284c7" strokeWidth="2" />
|
|
<circle cx="38" cy="16" r="4.5" fill="#0284c7" stroke="#ffffff" strokeWidth="1.5" />
|
|
<circle cx="10" cy="32" r="2.5" fill="#38bdf8" />
|
|
<defs>
|
|
<linearGradient id="sidebarStarGrad" x1="15" y1="9" x2="33" y2="33" gradientUnits="userSpaceOnUse">
|
|
<stop offset="0%" stopColor="#0284c7" />
|
|
<stop offset="100%" stopColor="#0369a1" />
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
);
|
|
|
|
return (
|
|
<aside
|
|
className={`bg-slate-50 border-r border-slate-200 flex flex-col justify-between py-6 z-20 select-none transition-all duration-355 cubic-bezier(0.4, 0, 0.2, 1) ${
|
|
isCollapsed ? 'w-16 px-2' : 'w-64 px-4'
|
|
}`}
|
|
>
|
|
<div>
|
|
{/* 系统LOGO与折叠控制区 */}
|
|
<div className={`mb-8 flex items-center transition-all duration-300 ${
|
|
isCollapsed ? 'justify-center px-0' : 'justify-between px-3'
|
|
}`}>
|
|
{/* Logo & 标题文字 */}
|
|
<div className="flex items-center gap-3 min-w-0">
|
|
{/* Logo按钮 (只在折叠状态下可点击展开) */}
|
|
<button
|
|
type="button"
|
|
disabled={!isCollapsed}
|
|
onClick={() => setIsCollapsed(false)}
|
|
className={`flex items-center justify-center shrink-0 rounded-xl transition-all duration-300 ${
|
|
isCollapsed
|
|
? 'w-11 h-11 bg-white hover:bg-sky-50/50 border border-slate-200 cursor-pointer shadow-xs hover:shadow-sm hover:scale-105 active:scale-95'
|
|
: 'w-9 h-9 bg-transparent border border-transparent cursor-default'
|
|
}`}
|
|
title={isCollapsed ? "展开导航" : undefined}
|
|
>
|
|
<div className={`transition-all duration-300 flex items-center justify-center ${isCollapsed ? 'w-8 h-8' : 'w-9 h-9'}`}>
|
|
{renderLogo()}
|
|
</div>
|
|
</button>
|
|
|
|
{/* 系统说明文字 */}
|
|
<div
|
|
className={`flex flex-col transition-all duration-300 origin-left ${
|
|
isCollapsed
|
|
? 'opacity-0 max-w-0 scale-95 -translate-x-2 pointer-events-none select-none overflow-hidden h-0'
|
|
: 'opacity-100 max-w-[150px] scale-100 translate-x-0'
|
|
}`}
|
|
>
|
|
<h1 className="text-sm font-bold text-slate-800 tracking-wider whitespace-nowrap">AstroResearch</h1>
|
|
<span className="text-[11px] text-slate-500 block font-medium font-sans whitespace-nowrap">天文学科研辅助系统</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 折叠控制按钮 (圆润悬浮效果) */}
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsCollapsed(true)}
|
|
className={`p-1.5 rounded-full bg-slate-100 hover:bg-slate-200 border border-slate-200 text-slate-500 hover:text-slate-800 transition-all duration-300 cursor-pointer shadow-2xs shrink-0 flex items-center justify-center hover:scale-105 active:scale-95 ${
|
|
isCollapsed
|
|
? 'opacity-0 scale-75 pointer-events-none w-0 h-0 p-0 border-0 overflow-hidden'
|
|
: 'opacity-100 scale-100'
|
|
}`}
|
|
title="收起导航"
|
|
>
|
|
<ChevronLeft className="w-3.5 h-3.5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* 导航菜单列表 */}
|
|
<nav className="space-y-1">
|
|
{[
|
|
{ id: 'search', label: '统一检索', icon: Search },
|
|
{ id: 'library', label: '馆藏管理', icon: Library },
|
|
{ id: 'sync', label: '批量任务', icon: RefreshCw },
|
|
{ id: 'reader', label: '双语阅读', icon: BookOpen },
|
|
{ id: 'agent', label: '智能科研', icon: Sparkles },
|
|
{ id: 'citation', label: '引用星系', icon: GitFork },
|
|
].map((tab: { id: string; label: string; icon: any; disabled?: boolean }) => {
|
|
const Icon = tab.icon;
|
|
const isActive = activeTab === tab.id;
|
|
return (
|
|
<button
|
|
key={tab.id}
|
|
disabled={tab.disabled}
|
|
title={isCollapsed ? tab.label : undefined}
|
|
onClick={() => {
|
|
setActiveTab(tab.id as any);
|
|
if (tab.id === 'citation' && selectedPaper) {
|
|
loadCitations(selectedPaper.bibcode);
|
|
}
|
|
}}
|
|
className={`w-full flex items-center rounded-lg text-xs font-semibold tracking-wider transition-all duration-300 border ${
|
|
isCollapsed ? 'px-2 py-2.5 justify-center' : 'px-3 py-2.5'
|
|
} ${
|
|
isActive
|
|
? 'bg-sky-50 border-sky-200 text-sky-700 shadow-sm'
|
|
: tab.disabled
|
|
? 'opacity-30 cursor-not-allowed border-transparent text-slate-400'
|
|
: 'border-transparent text-slate-650 hover:bg-slate-100 hover:text-slate-800'
|
|
}`}
|
|
>
|
|
<Icon className={`w-4 h-4 shrink-0 transition-colors duration-300 ${isActive ? 'text-sky-600' : 'text-slate-500'}`} />
|
|
<span
|
|
className={`truncate transition-all duration-300 origin-left ${
|
|
isCollapsed
|
|
? 'opacity-0 max-w-0 pointer-events-none select-none overflow-hidden scale-90 -translate-x-2'
|
|
: 'opacity-100 max-w-[150px] scale-100 translate-x-0 ml-3'
|
|
}`}
|
|
>
|
|
{tab.label}
|
|
</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
</div>
|
|
|
|
{/* 底部当前选定文献提示 (平滑动画版本) */}
|
|
<div className="space-y-4">
|
|
{selectedPaper ? (
|
|
<div
|
|
className={`relative overflow-hidden transition-all duration-300 border rounded-lg ${
|
|
isCollapsed
|
|
? 'p-0 border-transparent bg-transparent flex justify-center'
|
|
: 'p-3.5 border-sky-100 bg-sky-50/50'
|
|
}`}
|
|
title={isCollapsed ? `当前选定文献: ${selectedPaper.title}` : undefined}
|
|
>
|
|
{/* 折叠下的微型图书图标 */}
|
|
<div
|
|
className={`transition-all duration-300 flex items-center justify-center rounded-lg border border-sky-100 bg-sky-50 text-sky-600 shrink-0 shadow-sm ${
|
|
isCollapsed
|
|
? 'w-9 h-9 opacity-100 scale-100'
|
|
: 'w-0 h-0 opacity-0 scale-75 overflow-hidden'
|
|
}`}
|
|
>
|
|
<BookOpen className="w-4 h-4" />
|
|
</div>
|
|
|
|
{/* 展开下的完整详情 */}
|
|
<div
|
|
className={`transition-all duration-300 origin-left ${
|
|
isCollapsed
|
|
? 'opacity-0 max-w-0 max-h-0 pointer-events-none select-none overflow-hidden'
|
|
: 'opacity-100 max-w-[200px] max-h-40'
|
|
}`}
|
|
>
|
|
<span className="text-[9px] font-bold text-sky-600 tracking-widest block mb-1">当前选定文献</span>
|
|
<h4 className="text-xs text-slate-800 font-bold line-clamp-2 mb-2 leading-relaxed">{selectedPaper.title}</h4>
|
|
<div className="flex items-center justify-between text-[10px] font-medium text-slate-500 gap-2">
|
|
<span className="shrink-0">发表年份: {selectedPaper.year}</span>
|
|
<span className="truncate max-w-[90px] font-mono">{selectedPaper.bibcode}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div
|
|
className={`relative overflow-hidden transition-all duration-300 border rounded-lg ${
|
|
isCollapsed
|
|
? 'p-0 border-transparent bg-transparent flex justify-center'
|
|
: 'p-3 border-slate-200 bg-slate-100/30'
|
|
}`}
|
|
title={isCollapsed ? "未选定研究目标" : undefined}
|
|
>
|
|
{/* 折叠下的微型馆藏图标 */}
|
|
<div
|
|
className={`transition-all duration-300 flex items-center justify-center rounded-lg border border-slate-200 bg-slate-100/30 text-slate-400 shrink-0 ${
|
|
isCollapsed
|
|
? 'w-9 h-9 opacity-100 scale-100'
|
|
: 'w-0 h-0 opacity-0 scale-75 overflow-hidden'
|
|
}`}
|
|
>
|
|
<Library className="w-4 h-4" />
|
|
</div>
|
|
|
|
{/* 展开下的提示文字 */}
|
|
<div
|
|
className={`text-center transition-all duration-300 origin-left ${
|
|
isCollapsed
|
|
? 'opacity-0 max-w-0 max-h-0 pointer-events-none select-none overflow-hidden'
|
|
: 'opacity-100 max-w-[200px] max-h-12'
|
|
}`}
|
|
>
|
|
<span className="text-[10px] text-slate-400 font-medium tracking-wide">未选定研究目标</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|