refactor: 大文件模块化拆分、自进化Skill管线、前端设计系统统一
后端: - runtime: 拆分 AgentConfig/events/duplicate_detector 为独立模块 - error_recovery: 1499行单体拆为 classification(21种FailoverReason)/overflow/mod - executor: 提取 helpers.rs (PreparedCall/ToolExecutionResult/execute_single_tool) - skills: 新增 SkillCreator + SelfImprovePipeline(模式检测→自动生成SKILL.md→质量审查) - clients/llm: 拆分为 chat/embedding/types 三个子模块 - services/download: 1548行拆为 mod/headers(反爬+SSRF)/strategies(多级回退) - services/batch/asset: 1264行拆为 mod/helpers/process 前端: - 设计系统统一: sky/indigo → blueprint 色系, rounded-xl→lg, shadow-lg→sm - 删除 Vite 模板残留 App.css - GlobalDialog/PaperDetailModal/UncachedPaperModal 提取公共 BaseModal 组件
This commit is contained in:
@@ -1,184 +0,0 @@
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
@@ -244,7 +244,7 @@ export default function App() {
|
||||
<div className="absolute top-1/4 left-1/4 w-[500px] h-[500px] bg-sky-200/20 rounded-full blur-3xl" />
|
||||
<div className="absolute bottom-1/4 right-1/4 w-[500px] h-[500px] bg-indigo-200/20 rounded-full blur-3xl" />
|
||||
|
||||
<div className="console-panel rounded-xl p-8 max-w-sm w-full mx-4 shadow-sm z-10 relative bg-white border border-[#d2d8e2]">
|
||||
<div className="console-panel rounded-lg p-8 max-w-sm w-full mx-4 shadow-sm z-10 relative bg-white border border-[#d2d8e2]">
|
||||
<div className="flex flex-col items-center text-center mb-7 select-none">
|
||||
<div className="w-14 h-14 mb-3">
|
||||
<svg width="100%" height="100%" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-full h-full">
|
||||
|
||||
@@ -315,7 +315,7 @@ export function AIAssistantPanel({
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => handleSend(q)}
|
||||
className="w-full text-left bg-white hover:bg-slate-100 border border-slate-200 rounded-lg p-3 text-xs text-slate-700 leading-relaxed font-medium transition-all shadow-sm cursor-pointer hover:border-sky-300"
|
||||
className="w-full text-left bg-white hover:bg-slate-55 border border-slate-200 rounded-md p-3 text-xs text-slate-700 leading-relaxed font-medium transition-all cursor-pointer hover:border-slate-350"
|
||||
>
|
||||
{q}
|
||||
</button>
|
||||
@@ -333,10 +333,10 @@ export function AIAssistantPanel({
|
||||
{msg.sender === 'user' ? '我' : '天文科研助手'}
|
||||
</span>
|
||||
<div
|
||||
className={`max-w-[95%] rounded-xl px-4 py-3 text-xs leading-relaxed font-medium shadow-sm border ${
|
||||
className={`max-w-[95%] rounded-lg px-4 py-3 text-xs leading-relaxed font-medium shadow-xs border ${
|
||||
msg.sender === 'user'
|
||||
? 'bg-sky-600 text-white border-sky-600 select-text'
|
||||
: 'bg-white text-slate-800 border-slate-200 select-text'
|
||||
? 'bg-blueprint text-white border-blueprint select-text'
|
||||
: 'bg-white text-slate-800 border-slate-250 select-text'
|
||||
}`}
|
||||
>
|
||||
{msg.sender === 'user' ? (
|
||||
@@ -354,9 +354,9 @@ export function AIAssistantPanel({
|
||||
<div className="space-y-2.5">
|
||||
{/* 时间线条目(思考/工具调用/子代理容器)*/}
|
||||
{msg.timeline.length > 0 && (
|
||||
<div className="border border-slate-100 bg-slate-50/50 rounded-lg p-2 select-none">
|
||||
<div className="text-[10px] font-bold text-purple-500 flex items-center gap-1 mb-2 px-1">
|
||||
<Brain className="w-3.5 h-3.5" />
|
||||
<div className="border border-slate-205 bg-slate-100/30 rounded-md p-2 select-none">
|
||||
<div className="text-[10px] font-bold text-slate-600 flex items-center gap-1.5 mb-2 px-1">
|
||||
<Brain className="w-3.5 h-3.5 text-slate-450" />
|
||||
<span>推理与工具链 ({msg.timeline.length} 项)</span>
|
||||
</div>
|
||||
<div className="pl-4 border-l border-slate-200 space-y-2">
|
||||
@@ -442,7 +442,7 @@ export function AIAssistantPanel({
|
||||
}}
|
||||
className="absolute bottom-0 left-0 right-0 p-3 bg-transparent pointer-events-none shrink-0"
|
||||
>
|
||||
<div className="bg-slate-50 border border-slate-200/60 rounded-2xl p-2.5 focus-within:bg-white focus-within:border-sky-500 focus-within:ring-1 focus-within:ring-sky-500/10 transition-all relative pointer-events-auto shadow-md flex flex-col gap-2">
|
||||
<div className="bg-slate-50 border border-slate-300 rounded-lg p-2.5 focus-within:bg-white focus-within:border-blueprint focus-within:ring-1 focus-within:ring-blueprint/10 transition-all relative pointer-events-auto shadow-sm flex flex-col gap-2">
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
|
||||
@@ -64,7 +64,7 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
radius,
|
||||
color: isActive ? '#0284c7' : '#475569',
|
||||
color: isActive ? '#0369a1' : '#475569',
|
||||
type: 'center',
|
||||
inDb: true,
|
||||
});
|
||||
@@ -86,7 +86,7 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
radius: 8, // 初始值,稍后按连线数重算
|
||||
color: '#d97706',
|
||||
color: '#b45309',
|
||||
type: 'reference',
|
||||
inDb,
|
||||
});
|
||||
@@ -112,7 +112,7 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
radius: 8, // 初始值,稍后按连线数重算
|
||||
color: '#0891b2',
|
||||
color: '#0e7490',
|
||||
type: 'citation',
|
||||
inDb,
|
||||
});
|
||||
@@ -221,12 +221,12 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
ctx.scale(scale, scale);
|
||||
ctx.translate(-cx, -cy);
|
||||
|
||||
// 绘制背景宇宙引力线 & 刻度圈
|
||||
// 绘制背景宇宙引力线 & 刻度圈 - 极其素雅的学术引力线
|
||||
const centerNode = nodes.find(n => n.id === activeNetwork.bibcode);
|
||||
if (centerNode) {
|
||||
ctx.strokeStyle = 'rgba(148, 163, 184, 0.15)';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([4, 6]);
|
||||
ctx.strokeStyle = 'rgba(148, 163, 184, 0.08)';
|
||||
ctx.lineWidth = 0.75;
|
||||
ctx.setLineDash([3, 5]);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerNode.x, centerNode.y, 130, 0, Math.PI * 2);
|
||||
@@ -236,8 +236,8 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
ctx.arc(centerNode.x, centerNode.y, 180, 0, Math.PI * 2);
|
||||
ctx.stroke();
|
||||
|
||||
// 动态圈
|
||||
ctx.strokeStyle = 'rgba(2, 132, 199, 0.06)';
|
||||
// 动态引力波圈 - 调低对比度,使其呈极其微弱安静的向外平滑脉冲
|
||||
ctx.strokeStyle = 'rgba(3, 105, 161, 0.025)';
|
||||
ctx.setLineDash([]);
|
||||
const pulseRadius = 130 + (frameCount % 120) * 0.4;
|
||||
ctx.beginPath();
|
||||
@@ -245,8 +245,8 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 绘制连线
|
||||
ctx.lineWidth = 1;
|
||||
// 绘制连线 - 精细线宽
|
||||
ctx.lineWidth = 0.75;
|
||||
links.forEach(link => {
|
||||
const sourceNode = nodes.find(n => n.id === link.source);
|
||||
const targetNode = nodes.find(n => n.id === link.target);
|
||||
@@ -254,12 +254,13 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(sourceNode.x, sourceNode.y);
|
||||
ctx.lineTo(targetNode.x, targetNode.y);
|
||||
ctx.strokeStyle = 'rgba(148, 163, 184, 0.25)';
|
||||
ctx.strokeStyle = 'rgba(203, 213, 225, 0.22)';
|
||||
ctx.stroke();
|
||||
}
|
||||
});
|
||||
|
||||
// 绘制节点
|
||||
ctx.lineWidth = 1;
|
||||
nodes.forEach(node => {
|
||||
const isHovered = hoveredNode?.id === node.id;
|
||||
|
||||
@@ -267,8 +268,8 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
ctx.globalAlpha = node.inDb ? 1.0 : 0.35; // 未入库文献透明度降为 0.35
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, node.radius + (isHovered ? 6 : 3), 0, Math.PI * 2);
|
||||
ctx.fillStyle = node.color + (isHovered ? '25' : '0f');
|
||||
ctx.arc(node.x, node.y, node.radius + (isHovered ? 5 : 2), 0, Math.PI * 2);
|
||||
ctx.fillStyle = node.color + (isHovered ? '20' : '0a');
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
@@ -277,13 +278,13 @@ export function CitationGalaxyCanvas({ networks, activeNetwork, nodeLimit, onNod
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, node.radius + (isHovered ? 4 : 2), 0, Math.PI * 2);
|
||||
ctx.strokeStyle = node.color + '40';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.arc(node.x, node.y, node.radius + (isHovered ? 3 : 1.5), 0, Math.PI * 2);
|
||||
ctx.strokeStyle = node.color + '30';
|
||||
ctx.lineWidth = 0.75;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = isHovered ? '#0284c7' : '#334155';
|
||||
ctx.font = isHovered ? 'bold 10px sans-serif' : '9px sans-serif';
|
||||
ctx.fillStyle = isHovered ? '#0369a1' : '#475569';
|
||||
ctx.font = isHovered ? 'bold 9.5px sans-serif' : '9px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(node.label, node.x, node.y + node.radius + (isHovered ? 16 : 12));
|
||||
|
||||
|
||||
@@ -47,20 +47,20 @@ export function CustomSelect({
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={`w-full flex items-center justify-between gap-2 bg-white border border-slate-250 hover:border-slate-350 rounded-lg px-2.5 py-2 text-xs font-semibold text-slate-700 transition-all text-left outline-none cursor-pointer ${
|
||||
isOpen ? 'border-sky-500 ring-1 ring-sky-500 bg-white' : ''
|
||||
className={`w-full flex items-center justify-between gap-2 bg-white border border-slate-250 hover:border-slate-350 rounded-md px-2.5 py-2 text-xs font-semibold text-slate-700 transition-all text-left outline-none cursor-pointer ${
|
||||
isOpen ? 'border-blueprint ring-1 ring-blueprint bg-white' : ''
|
||||
} ${disabled ? 'bg-slate-50 text-slate-450 cursor-not-allowed border-slate-200' : ''}`}
|
||||
>
|
||||
<span className="truncate">{selectedOption?.label}</span>
|
||||
<ChevronDown
|
||||
className={`w-3.5 h-3.5 text-slate-400 transition-transform duration-200 shrink-0 ${
|
||||
isOpen ? 'rotate-180 text-sky-500' : ''
|
||||
isOpen ? 'rotate-180 text-blueprint' : ''
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{isOpen && !disabled && (
|
||||
<div className="absolute left-0 mt-1.5 w-full min-w-[150px] bg-white border border-slate-200 rounded-lg shadow-lg py-1 z-50 max-h-60 overflow-y-auto scrollbar-thin">
|
||||
<div className="absolute left-0 mt-1.5 w-full min-w-[150px] bg-white border border-slate-200 rounded-md shadow-md py-1 z-50 max-h-60 overflow-y-auto scrollbar-thin">
|
||||
{options.map(option => {
|
||||
const isSelected = option.value === value;
|
||||
return (
|
||||
@@ -73,7 +73,7 @@ export function CustomSelect({
|
||||
}}
|
||||
className={`w-full text-left px-3 py-2 text-xs transition-colors block cursor-pointer outline-none ${
|
||||
isSelected
|
||||
? 'bg-sky-50 text-sky-700 font-bold'
|
||||
? 'bg-blueprint/5 text-blueprint font-bold'
|
||||
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900 font-medium'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import React from 'react';
|
||||
import type { StandardPaper } from '../types';
|
||||
import { getDoctypeBadge } from '../utils/paper';
|
||||
|
||||
interface PaperCardProps {
|
||||
paper: StandardPaper;
|
||||
isSelected?: boolean;
|
||||
onClick?: () => void;
|
||||
variant?: 'list' | 'grid';
|
||||
headerActions?: React.ReactNode;
|
||||
statusBadge?: React.ReactNode;
|
||||
abstract?: React.ReactNode;
|
||||
footerLeft?: React.ReactNode;
|
||||
footerRight?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function PaperCard({
|
||||
paper,
|
||||
isSelected = false,
|
||||
onClick,
|
||||
variant = 'list',
|
||||
headerActions,
|
||||
statusBadge,
|
||||
abstract,
|
||||
footerLeft,
|
||||
footerRight,
|
||||
}: PaperCardProps) {
|
||||
if (variant === 'grid') {
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="console-panel p-5 rounded-lg border border-slate-200 hover:border-slate-400 bg-white flex flex-col justify-between relative overflow-hidden group transition-all cursor-pointer shadow-sm select-none"
|
||||
>
|
||||
{/* Top Right Corner Status Badge */}
|
||||
{statusBadge}
|
||||
|
||||
<div className="pr-10">
|
||||
<div>
|
||||
<h3 className="font-bold text-xs text-slate-900 line-clamp-2 hover:text-blueprint transition-all leading-relaxed">
|
||||
{getDoctypeBadge(paper.doctype)}
|
||||
<span className="align-middle">{paper.title}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 font-semibold mt-1.5 uppercase">
|
||||
作者: {paper.authors.slice(0, 2).join(', ')}{paper.authors.length > 2 ? ' 等' : ''} | 发表年份: {paper.year}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Area */}
|
||||
{(footerLeft || footerRight) && (
|
||||
<div className="flex items-center justify-between border-t border-slate-100 pt-3 mt-4 text-[10px]">
|
||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||
{footerLeft}
|
||||
</div>
|
||||
{footerRight && (
|
||||
<div className="flex items-center gap-2 shrink-0 self-end">
|
||||
{footerRight}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// default 'list' variant
|
||||
return (
|
||||
<div
|
||||
className={`console-panel p-6 rounded-lg border transition-all relative ${
|
||||
isSelected ? 'border-slate-800 bg-slate-50/30' : 'border-slate-200 hover:border-slate-350 hover:bg-white'
|
||||
}`}
|
||||
>
|
||||
{/* Selection indicator side bar */}
|
||||
{isSelected && <div className="absolute top-0 left-0 w-1.5 h-full bg-slate-800 rounded-l-lg" />}
|
||||
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-start gap-4 mb-3">
|
||||
<div className="flex-1">
|
||||
<h3
|
||||
className="font-bold text-sm text-slate-900 hover:text-slate-800 cursor-pointer transition-all line-clamp-2 leading-relaxed"
|
||||
onClick={onClick}
|
||||
>
|
||||
{getDoctypeBadge(paper.doctype)}
|
||||
<span className="align-middle">{paper.title}</span>
|
||||
</h3>
|
||||
<p className="text-xs text-slate-500 font-medium mt-2 leading-snug">
|
||||
作者: <span className="text-slate-800">{paper.authors.slice(0, 5).join(', ')}{paper.authors.length > 5 ? ' 等' : ''}</span> • 年份: <span className="text-slate-850 font-bold">{paper.year}</span> • 出版期刊: <span className="italic text-slate-700">{paper.pub_journal || '未标注'}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{headerActions && (
|
||||
<div className="flex gap-2 shrink-0 items-center">
|
||||
{headerActions}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{abstract}
|
||||
|
||||
{/* Footer Area */}
|
||||
{(footerLeft || footerRight) && (
|
||||
<div className="flex flex-col sm:flex-row justify-between items-stretch sm:items-center border-t border-slate-100 pt-4 gap-3">
|
||||
{footerLeft && (
|
||||
<div className="flex gap-2">
|
||||
{footerLeft}
|
||||
</div>
|
||||
)}
|
||||
{footerRight && (
|
||||
<div className="text-xs text-slate-450 flex flex-wrap gap-x-4 gap-y-1 font-mono">
|
||||
{footerRight}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function AgentInputArea({
|
||||
}}
|
||||
className="max-w-4xl mx-auto w-full pointer-events-auto"
|
||||
>
|
||||
<div className="bg-slate-50 border border-slate-200/60 rounded-2xl p-2.5 focus-within:bg-white focus-within:border-sky-500 focus-within:ring-1 focus-within:ring-sky-500/10 transition-all shadow-lg flex flex-col gap-2">
|
||||
<div className="bg-slate-50 border border-slate-200/60 rounded-lg p-2.5 focus-within:bg-white focus-within:border-blueprint focus-within:ring-1 focus-within:ring-blueprint/10 transition-all shadow-sm flex flex-col gap-2">
|
||||
{/* 图片预览 */}
|
||||
{pendingImage && (
|
||||
<div className="relative inline-block mb-1 group select-none self-start">
|
||||
@@ -113,8 +113,8 @@ export function AgentInputArea({
|
||||
title="上传或粘贴图片(也可直接 Ctrl+V 粘贴)"
|
||||
className={`p-1.5 rounded-lg border text-[10px] font-bold transition-all cursor-pointer flex items-center gap-1 ${
|
||||
pendingImage
|
||||
? 'bg-sky-50 border-sky-300 text-sky-700'
|
||||
: 'bg-white border-slate-200 text-slate-400 hover:text-sky-600 hover:border-sky-200'
|
||||
? 'bg-blueprint/5 border-blueprint/30 text-blueprint'
|
||||
: 'bg-white border-slate-200 text-slate-400 hover:text-blueprint hover:border-blueprint/20'
|
||||
} disabled:opacity-60`}
|
||||
>
|
||||
<Paperclip className="w-3.5 h-3.5" />
|
||||
@@ -134,11 +134,11 @@ export function AgentInputArea({
|
||||
title={m.description}
|
||||
className={`px-2 py-1.5 rounded-md text-[10px] font-extrabold transition-all border cursor-pointer flex items-center gap-1 shrink-0 ${
|
||||
isSelected
|
||||
? 'bg-white text-indigo-700 shadow-xs border-slate-200/50'
|
||||
: 'bg-transparent border-transparent text-slate-400 hover:text-indigo-600'
|
||||
? 'bg-white text-blueprint shadow-xs border-slate-200/50'
|
||||
: 'bg-transparent border-transparent text-slate-400 hover:text-blueprint'
|
||||
} disabled:opacity-60`}
|
||||
>
|
||||
<IconComp className={`w-3.5 h-3.5 ${isSelected ? 'text-indigo-500' : ''}`} />
|
||||
<IconComp className={`w-3.5 h-3.5 ${isSelected ? 'text-blueprint' : ''}`} />
|
||||
<span className="hidden sm:inline">{m.name}</span>
|
||||
</button>
|
||||
);
|
||||
@@ -156,11 +156,11 @@ export function AgentInputArea({
|
||||
title={thinking ? '思考模式已开启(启用 LLM 推理过程)' : '思考模式已关闭(点击开启)'}
|
||||
className={`px-2.5 py-1.5 rounded-lg border text-[10px] font-bold transition-all cursor-pointer flex items-center gap-1 shrink-0 ${
|
||||
thinking
|
||||
? 'bg-sky-50 border-sky-205 text-sky-700 shadow-3xs'
|
||||
: 'bg-white border-slate-200 text-slate-400 hover:text-sky-600 hover:border-sky-200'
|
||||
? 'bg-blueprint/5 border-blueprint/30 text-blueprint shadow-3xs'
|
||||
: 'bg-white border-slate-200 text-slate-400 hover:text-blueprint hover:border-blueprint/20'
|
||||
} disabled:opacity-60`}
|
||||
>
|
||||
<Brain className={`w-3.5 h-3.5 ${thinking ? 'text-sky-500' : ''}`} />
|
||||
<Brain className={`w-3.5 h-3.5 ${thinking ? 'text-blueprint' : ''}`} />
|
||||
<span className="hidden sm:inline">思考</span>
|
||||
</button>
|
||||
</>
|
||||
@@ -172,7 +172,7 @@ export function AgentInputArea({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onStop}
|
||||
className="p-2 rounded-xl bg-slate-800 hover:bg-slate-950 text-white transition-colors cursor-pointer flex items-center justify-center shadow-xs"
|
||||
className="p-2 rounded-md bg-slate-800 hover:bg-slate-950 text-white transition-colors cursor-pointer flex items-center justify-center shadow-xs"
|
||||
title="手动停止执行"
|
||||
>
|
||||
<Square className="w-3.5 h-3.5 fill-white" />
|
||||
@@ -181,7 +181,7 @@ export function AgentInputArea({
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!input.trim()}
|
||||
className="p-2 rounded-xl bg-sky-600 hover:bg-sky-700 text-white disabled:bg-slate-100 disabled:text-slate-350 transition-colors cursor-pointer flex items-center justify-center shadow-xs"
|
||||
className="p-2 rounded-md bg-blueprint hover:bg-[#0d5988] text-white disabled:bg-slate-100 disabled:text-slate-350 transition-colors cursor-pointer flex items-center justify-center shadow-xs"
|
||||
>
|
||||
<Send className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
|
||||
@@ -200,8 +200,8 @@ export function AgentMessageList({
|
||||
onClick={() => { setShowMetrics(!showMetrics); setShowAuditLog(false); }}
|
||||
className={`p-1.5 rounded-lg border text-xs font-bold transition-all cursor-pointer flex items-center gap-1 ${
|
||||
showMetrics
|
||||
? 'bg-sky-50 border-sky-200 text-sky-700'
|
||||
: 'bg-white border-slate-200 text-slate-500 hover:text-sky-600 hover:border-sky-200'
|
||||
? 'bg-blueprint/5 border-blueprint/20 text-blueprint'
|
||||
: 'bg-white border-slate-200 text-slate-500 hover:text-blueprint hover:border-blueprint/20'
|
||||
}`}
|
||||
title="运行指标"
|
||||
>
|
||||
@@ -276,9 +276,9 @@ export function AgentMessageList({
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => handleSend(q)}
|
||||
className="w-full text-left bg-white hover:bg-sky-50/20 border border-slate-200 hover:border-sky-300 rounded-xl p-3.5 text-xs text-slate-700 leading-relaxed font-medium transition-all shadow-2xs hover:shadow-xs cursor-pointer flex flex-col gap-1 group"
|
||||
className="w-full text-left bg-white hover:bg-blueprint/5 border border-slate-200 hover:border-blueprint/30 rounded-lg p-3 text-xs text-slate-700 leading-relaxed font-medium transition-all shadow-2xs hover:shadow-xs cursor-pointer flex flex-col gap-1 group"
|
||||
>
|
||||
<span className="group-hover:text-sky-700 font-semibold">{q}</span>
|
||||
<span className="group-hover:text-blueprint font-semibold">{q}</span>
|
||||
<span className="text-[9px] text-slate-400 uppercase font-bold tracking-wider">点击开始</span>
|
||||
</button>
|
||||
))}
|
||||
@@ -303,9 +303,9 @@ export function AgentMessageList({
|
||||
<Rewind className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
)}
|
||||
<div className="rounded-2xl px-4 py-3 text-xs leading-relaxed font-semibold shadow-2xs border bg-sky-600 text-white border-sky-600 select-text flex-1 space-y-2">
|
||||
<div className="rounded-lg px-4 py-3 text-xs leading-relaxed font-semibold shadow-2xs border bg-blueprint text-white border-blueprint select-text flex-1 space-y-2">
|
||||
{turn.imagePath && (
|
||||
<img src={turn.imagePath} alt="用户上传的图片" className="max-h-48 rounded-lg border border-sky-500/30" />
|
||||
<img src={turn.imagePath} alt="用户上传的图片" className="max-h-48 rounded-lg border border-blueprint/30" />
|
||||
)}
|
||||
<div>{turn.question}</div>
|
||||
</div>
|
||||
@@ -334,9 +334,9 @@ export function AgentMessageList({
|
||||
{/* 当前提问 */}
|
||||
<div className="flex flex-col items-end space-y-1">
|
||||
<span className="text-[10px] font-bold text-slate-400 px-1">我</span>
|
||||
<div className="max-w-[85%] rounded-2xl px-4 py-3 text-xs leading-relaxed font-semibold shadow-2xs border bg-sky-600 text-white border-sky-600 select-text space-y-2">
|
||||
<div className="max-w-[85%] rounded-lg px-4 py-3 text-xs leading-relaxed font-semibold shadow-2xs border bg-blueprint text-white border-blueprint select-text space-y-2">
|
||||
{activeTurn.imagePath && (
|
||||
<img src={activeTurn.imagePath} alt="用户上传的图片" className="max-h-48 rounded-lg border border-sky-500/30" />
|
||||
<img src={activeTurn.imagePath} alt="用户上传的图片" className="max-h-48 rounded-lg border border-blueprint/30" />
|
||||
)}
|
||||
<div>{activeTurn.question}</div>
|
||||
</div>
|
||||
@@ -353,10 +353,10 @@ export function AgentMessageList({
|
||||
{activeTurn.finalAnswer && (
|
||||
<div className="flex flex-col items-start space-y-1">
|
||||
<span className="text-[10px] font-bold text-slate-400 px-1 flex items-center gap-1">
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-sky-600 animate-pulse" />
|
||||
<CheckCircle2 className="w-3.5 h-3.5 text-blueprint" />
|
||||
<span>科研智能体输出最终结论</span>
|
||||
</span>
|
||||
<div className="max-w-[90%] rounded-2xl px-5 py-4 text-xs leading-relaxed font-semibold shadow-xs border bg-white text-slate-800 border-slate-200 select-text prose prose-sm max-w-none prose-headings:text-slate-900 prose-headings:font-bold prose-strong:text-slate-900 prose-code:text-sky-700 prose-img:rounded-lg">
|
||||
<div className="max-w-[90%] rounded-lg px-5 py-4 text-xs leading-relaxed font-semibold shadow-xs border bg-white text-slate-800 border-slate-200 select-text prose prose-sm max-w-none prose-headings:text-slate-900 prose-headings:font-bold prose-strong:text-slate-900 prose-code:text-blueprint prose-img:rounded-lg">
|
||||
<AgentMarkdown>{activeTurn.finalAnswer}</AgentMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
@@ -365,7 +365,7 @@ export function AgentMessageList({
|
||||
{/* 活跃的加载指示器 */}
|
||||
{streaming && !activeTurn.finalAnswer && !activeTurn.error && (
|
||||
<div className="flex items-center space-x-2 text-slate-500 pl-2">
|
||||
<Loader className="w-4 h-4 animate-spin text-sky-600" />
|
||||
<Loader className="w-4 h-4 animate-spin text-blueprint" />
|
||||
<span className="text-[10px] font-bold">智能体正在搜集信息进行推理分析...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -107,7 +107,7 @@ export function AgentMetricsPanel({ showAlert }: AgentMetricsPanelProps) {
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<BarChart3 className="w-4 h-4 text-sky-600" />
|
||||
<BarChart3 className="w-4 h-4 text-blueprint" />
|
||||
<h3 className="text-xs font-extrabold text-slate-800 tracking-wide">
|
||||
智能体运行指标
|
||||
</h3>
|
||||
@@ -115,7 +115,7 @@ export function AgentMetricsPanel({ showAlert }: AgentMetricsPanelProps) {
|
||||
<button
|
||||
onClick={fetchMetrics}
|
||||
disabled={loading}
|
||||
className="p-1.5 rounded-lg bg-slate-100 hover:bg-slate-200 text-slate-500 hover:text-slate-700 transition-colors cursor-pointer disabled:opacity-50"
|
||||
className="p-1.5 rounded-md bg-slate-100 hover:bg-slate-200 text-slate-500 hover:text-slate-700 transition-colors cursor-pointer disabled:opacity-50"
|
||||
title="刷新指标"
|
||||
>
|
||||
<RefreshCw className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} />
|
||||
@@ -124,7 +124,7 @@ export function AgentMetricsPanel({ showAlert }: AgentMetricsPanelProps) {
|
||||
|
||||
{loading && !metrics ? (
|
||||
<div className="flex items-center justify-center py-12 text-slate-400 gap-2">
|
||||
<Loader className="w-4 h-4 animate-spin text-sky-600" />
|
||||
<Loader className="w-4 h-4 animate-spin text-blueprint" />
|
||||
<span className="text-xs font-bold">加载运行指标中...</span>
|
||||
</div>
|
||||
) : metrics ? (
|
||||
@@ -228,15 +228,15 @@ function MetricCard({
|
||||
color: string;
|
||||
}) {
|
||||
const colorMap: Record<string, string> = {
|
||||
sky: 'border-sky-200 bg-sky-50 text-sky-700',
|
||||
emerald: 'border-emerald-200 bg-emerald-50 text-emerald-700',
|
||||
violet: 'border-violet-200 bg-violet-50 text-violet-700',
|
||||
rose: 'border-rose-200 bg-rose-50 text-rose-700',
|
||||
amber: 'border-amber-200 bg-amber-50 text-amber-700',
|
||||
sky: 'border-slate-200 bg-slate-50 text-blueprint',
|
||||
emerald: 'border-emerald-200 bg-emerald-50/40 text-emerald-700',
|
||||
violet: 'border-violet-200 bg-violet-50/40 text-violet-700',
|
||||
rose: 'border-rose-200 bg-rose-50/40 text-rose-700',
|
||||
amber: 'border-amber-200 bg-amber-50/40 text-amber-700',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`rounded-xl border p-3.5 ${colorMap[color] || colorMap.sky} transition-all`}>
|
||||
<div className={`rounded-md border p-3 ${colorMap[color] || colorMap.sky} transition-all`}>
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<span className="opacity-60">{icon}</span>
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider opacity-70">{label}</span>
|
||||
|
||||
@@ -42,13 +42,13 @@ export function AgentSessionSidebar({
|
||||
<div className="flex flex-col min-h-0 flex-1">
|
||||
<div className="p-4 border-b border-slate-200 bg-white flex items-center justify-between shrink-0">
|
||||
<span className="text-xs font-extrabold text-slate-800 tracking-wider flex items-center gap-1.5">
|
||||
<Clock className="w-3.5 h-3.5 text-sky-600" />
|
||||
<Clock className="w-3.5 h-3.5 text-blueprint" />
|
||||
{!collapsed && <span>会话历史</span>}
|
||||
</span>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<button
|
||||
onClick={onNewSession}
|
||||
className="p-1 rounded-md border border-slate-200 bg-white hover:bg-slate-50 text-slate-600 hover:text-slate-800 transition-all cursor-pointer shadow-2xs hover:scale-105"
|
||||
className="p-1 rounded-md border border-slate-200 bg-white hover:bg-slate-50 text-slate-600 hover:text-slate-800 transition-all cursor-pointer shadow-2xs"
|
||||
title="新建会话"
|
||||
>
|
||||
<Plus className="w-3.5 h-3.5" />
|
||||
@@ -71,7 +71,7 @@ export function AgentSessionSidebar({
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
placeholder="搜索历史会话或消息内容..."
|
||||
className="w-full pl-8 pr-7 py-1.5 rounded-lg bg-slate-50 border border-slate-200 text-slate-800 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:bg-white transition-all text-[11px] font-medium"
|
||||
className="w-full pl-8 pr-7 py-1.5 rounded-md bg-slate-50 border border-slate-200 text-slate-800 placeholder-slate-400 focus:outline-none focus:border-blueprint focus:bg-white transition-all text-[11px] font-medium"
|
||||
/>
|
||||
<div className="absolute left-2.5 top-2.5 text-slate-400">
|
||||
<Search className="w-3.5 h-3.5 text-slate-400" />
|
||||
@@ -91,7 +91,7 @@ export function AgentSessionSidebar({
|
||||
type="checkbox"
|
||||
checked={searchScopeOnlyCurrent}
|
||||
onChange={e => setSearchScopeOnlyCurrent(e.target.checked)}
|
||||
className="rounded text-sky-600 border-slate-300 focus:ring-sky-500 w-3 h-3 cursor-pointer"
|
||||
className="rounded text-blueprint border-slate-300 focus:ring-blueprint w-3 h-3 cursor-pointer"
|
||||
/>
|
||||
<span>仅搜索当前会话</span>
|
||||
</label>
|
||||
@@ -102,7 +102,7 @@ export function AgentSessionSidebar({
|
||||
{searchQuery.trim() ? (
|
||||
loadingSearch ? (
|
||||
<div className="flex items-center justify-center p-8 text-slate-400 text-xs gap-2">
|
||||
<Loader className="w-3.5 h-3.5 animate-spin text-sky-600" />
|
||||
<Loader className="w-3.5 h-3.5 animate-spin text-blueprint" />
|
||||
<span>检索历史记录中...</span>
|
||||
</div>
|
||||
) : searchResults.length === 0 ? (
|
||||
@@ -119,9 +119,9 @@ export function AgentSessionSidebar({
|
||||
<button
|
||||
key={`${result.session_id}-${idx}`}
|
||||
onClick={() => setCurrentSessionId(result.session_id)}
|
||||
className={`w-full text-left p-2.5 rounded-lg border transition-all duration-200 flex flex-col gap-1 cursor-pointer ${
|
||||
className={`w-full text-left p-2.5 rounded-md border transition-all duration-200 flex flex-col gap-1 cursor-pointer ${
|
||||
isActive
|
||||
? 'bg-sky-50/70 border-sky-200 text-sky-850 font-bold shadow-2xs'
|
||||
? 'bg-blueprint/5 border-blueprint text-blueprint font-bold shadow-2xs'
|
||||
: 'border-transparent bg-white hover:bg-slate-100 text-slate-650 shadow-3xs'
|
||||
}`}
|
||||
>
|
||||
@@ -129,12 +129,12 @@ export function AgentSessionSidebar({
|
||||
<span className="text-[10px] font-bold text-slate-400 flex items-center gap-1">
|
||||
{isMessage ? (
|
||||
<>
|
||||
<MessageSquare className="w-3 h-3 text-purple-400" />
|
||||
<MessageSquare className="w-3 h-3 text-slate-400" />
|
||||
<span>{role === 'user' ? '提问' : '解答'}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BookOpen className="w-3 h-3 text-sky-500" />
|
||||
<BookOpen className="w-3 h-3 text-blueprint" />
|
||||
<span>会话</span>
|
||||
</>
|
||||
)}
|
||||
@@ -162,7 +162,7 @@ export function AgentSessionSidebar({
|
||||
// 正常的会话列表渲染
|
||||
loadingSessions ? (
|
||||
<div className="flex items-center justify-center p-8 text-slate-400 text-xs gap-2">
|
||||
<Loader className="w-3.5 h-3.5 animate-spin text-sky-600" />
|
||||
<Loader className="w-3.5 h-3.5 animate-spin text-blueprint" />
|
||||
<span>加载历史会话中...</span>
|
||||
</div>
|
||||
) : sessions.length === 0 ? (
|
||||
@@ -176,10 +176,10 @@ export function AgentSessionSidebar({
|
||||
<button
|
||||
key={session.session_id}
|
||||
onClick={() => setCurrentSessionId(session.session_id)}
|
||||
className={`w-full text-left p-3 rounded-lg border transition-all duration-200 group flex items-start justify-between gap-2 cursor-pointer ${
|
||||
className={`w-full text-left p-3 rounded-md border transition-all duration-200 group flex items-start justify-between gap-2 cursor-pointer ${
|
||||
isActive
|
||||
? 'bg-sky-50 border-sky-200 text-sky-850 font-bold shadow-2xs'
|
||||
: 'border-transparent bg-transparent hover:bg-slate-100 text-slate-650'
|
||||
? 'bg-blueprint/5 border-blueprint text-blueprint font-bold shadow-2xs'
|
||||
: 'border-transparent bg-transparent hover:bg-slate-100 text-slate-655'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0 flex flex-col gap-1 text-left">
|
||||
|
||||
@@ -134,13 +134,13 @@ export function AskUserQuestionCard({ onAnswered, onQuestionCountChange }: AskUs
|
||||
return (
|
||||
<div
|
||||
key={q.question_id}
|
||||
className="console-panel rounded-xl p-3 shadow-lg border-amber-200/80 bg-amber-50/45 transition-all pointer-events-auto text-xs"
|
||||
className="rounded-md border border-slate-200 border-l-4 border-l-blueprint bg-slate-50 p-4 transition-all pointer-events-auto text-xs shadow-sm"
|
||||
>
|
||||
{/* 标题栏 - 头部与问题放在同一行,可省略独立的问题文本框 */}
|
||||
<div className="mb-2 flex items-center justify-between border-b border-amber-100/40 pb-1.5 min-w-0">
|
||||
<div className="mb-2.5 flex items-center justify-between border-b border-slate-200 pb-2 min-w-0">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1 mr-3">
|
||||
<MessageCircle className="h-3.5 w-3.5 text-amber-550 shrink-0" />
|
||||
<span className="px-1.5 py-0.2 rounded bg-amber-100 border border-amber-200 text-[9px] font-extrabold text-amber-800 shrink-0">
|
||||
<MessageCircle className="h-3.5 w-3.5 text-blueprint shrink-0" />
|
||||
<span className="px-1.5 py-0.2 rounded bg-slate-200 border border-slate-300 text-[9px] font-bold text-slate-755 shrink-0">
|
||||
{q.header || '提问'}
|
||||
</span>
|
||||
<span className="font-extrabold text-slate-800 text-[11px] truncate flex-1" title={q.question || ''}>
|
||||
@@ -152,7 +152,7 @@ export function AskUserQuestionCard({ onAnswered, onQuestionCountChange }: AskUs
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(prev => ({ ...prev, [q.question_id]: !prev[q.question_id] }))}
|
||||
className="text-[10px] font-bold text-sky-600 hover:text-sky-850 cursor-pointer select-none"
|
||||
className="text-[10px] font-bold text-blueprint hover:underline cursor-pointer select-none"
|
||||
>
|
||||
{isExpanded ? '收起选项' : '展开选项'}
|
||||
</button>
|
||||
@@ -180,20 +180,20 @@ export function AskUserQuestionCard({ onAnswered, onQuestionCountChange }: AskUs
|
||||
key={idx}
|
||||
onClick={() => toggleOption(q.question_id, label, multiSelect)}
|
||||
disabled={isSubmitting}
|
||||
className={`w-full text-left px-2.5 py-1.5 rounded-lg border text-[11px] font-semibold transition-all cursor-pointer flex items-center gap-2 ${
|
||||
className={`w-full text-left px-2.5 py-1.5 rounded-md border text-[11px] font-semibold transition-all cursor-pointer flex items-center gap-2 ${
|
||||
selected
|
||||
? 'bg-sky-50/70 border-sky-300 text-sky-850 shadow-3xs'
|
||||
: 'bg-white border-slate-200/60 text-slate-700 hover:border-sky-200 hover:bg-sky-50/50'
|
||||
? 'bg-blueprint/5 border-blueprint text-blueprint font-bold'
|
||||
: 'bg-white border-slate-200/60 text-slate-700 hover:border-slate-300 hover:bg-slate-50'
|
||||
} disabled:opacity-50`}
|
||||
>
|
||||
{/* 选择指示器 */}
|
||||
{multiSelect ? (
|
||||
selected
|
||||
? <CheckSquare className="w-3.5 h-3.5 text-sky-600 shrink-0" />
|
||||
: <Square className="w-3.5 h-3.5 text-slate-450 shrink-0" />
|
||||
? <CheckSquare className="w-3.5 h-3.5 text-blueprint shrink-0" />
|
||||
: <Square className="w-3.5 h-3.5 text-slate-400 shrink-0" />
|
||||
) : (
|
||||
<div className={`w-3.5 h-3.5 rounded-full border-2 shrink-0 ${
|
||||
selected ? 'border-sky-600 bg-sky-600' : 'border-slate-350'
|
||||
selected ? 'border-blueprint bg-blueprint' : 'border-slate-300'
|
||||
}`}>
|
||||
{selected && (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
@@ -227,13 +227,13 @@ export function AskUserQuestionCard({ onAnswered, onQuestionCountChange }: AskUs
|
||||
disabled={isSubmitting}
|
||||
placeholder="补充说明(可选)..."
|
||||
rows={1}
|
||||
className="w-full bg-white border border-slate-200/60 rounded-lg px-2.5 py-1.5 text-[11px] text-slate-800 placeholder-slate-400 focus:outline-none focus:border-sky-300 focus:ring-1 focus:ring-sky-500/10 resize-none disabled:opacity-50 min-h-[32px] max-h-24 leading-normal"
|
||||
className="w-full bg-white border border-slate-200/60 rounded-md px-2.5 py-1.5 text-[11px] text-slate-800 placeholder-slate-400 focus:outline-none focus:border-blueprint focus:ring-1 focus:ring-blueprint/10 resize-none disabled:opacity-50 min-h-[32px] max-h-24 leading-normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 错误提示 */}
|
||||
{qError && (
|
||||
<div className="text-[10px] text-red-700 bg-red-50 border border-red-200 rounded-lg px-2.5 py-1.5 font-bold">
|
||||
<div className="text-[10px] text-red-700 bg-red-50 border border-red-200 rounded-md px-2.5 py-1.5 font-bold">
|
||||
{qError}
|
||||
</div>
|
||||
)}
|
||||
@@ -243,7 +243,7 @@ export function AskUserQuestionCard({ onAnswered, onQuestionCountChange }: AskUs
|
||||
<button
|
||||
onClick={() => handleSubmit(q.question_id)}
|
||||
disabled={isSubmitting}
|
||||
className="flex-1 flex items-center justify-center gap-1.5 bg-emerald-600 hover:bg-emerald-700 border border-emerald-600 hover:border-emerald-750 text-white rounded-lg py-1.5 text-[11px] font-extrabold transition-all cursor-pointer disabled:opacity-50 hover:scale-102 active:scale-98"
|
||||
className="flex-1 flex items-center justify-center gap-1.5 bg-emerald-700 hover:bg-emerald-800 border border-emerald-700 text-white rounded-md py-1.5 text-[11px] font-bold transition-colors cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
@@ -260,7 +260,7 @@ export function AskUserQuestionCard({ onAnswered, onQuestionCountChange }: AskUs
|
||||
<button
|
||||
onClick={() => dismissQuestion(q.question_id)}
|
||||
disabled={isSubmitting}
|
||||
className="px-3 bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 hover:border-red-300 rounded-lg text-[11px] font-extrabold transition-all cursor-pointer disabled:opacity-50 hover:scale-102 active:scale-98"
|
||||
className="px-3 bg-slate-100 hover:bg-slate-200 text-slate-700 border border-slate-250 rounded-md text-[11px] font-bold transition-colors cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
忽略
|
||||
</button>
|
||||
|
||||
@@ -73,7 +73,7 @@ export function AuditLogViewer({ sessionId, onClose }: AuditLogViewerProps) {
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<ScrollText className="w-4 h-4 text-sky-600" />
|
||||
<ScrollText className="w-4 h-4 text-blueprint" />
|
||||
<h3 className="text-xs font-extrabold text-slate-800 tracking-wide">
|
||||
会话审计日志
|
||||
</h3>
|
||||
@@ -91,15 +91,15 @@ export function AuditLogViewer({ sessionId, onClose }: AuditLogViewerProps) {
|
||||
{/* 汇总 */}
|
||||
{entries.length > 0 && (
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<div className="rounded-lg bg-emerald-50 border border-emerald-200 px-3 py-2 text-center">
|
||||
<div className="rounded-md bg-emerald-50/40 border border-emerald-200 px-3 py-2 text-center">
|
||||
<div className="text-xs font-extrabold text-emerald-700">{okCount}</div>
|
||||
<div className="text-[9px] font-bold text-emerald-500">成功</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-rose-50 border border-rose-200 px-3 py-2 text-center">
|
||||
<div className="rounded-md bg-rose-50/40 border border-rose-200 px-3 py-2 text-center">
|
||||
<div className="text-xs font-extrabold text-rose-700">{failCount}</div>
|
||||
<div className="text-[9px] font-bold text-rose-500">失败</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-slate-50 border border-slate-200 px-3 py-2 text-center">
|
||||
<div className="rounded-md bg-slate-50 border border-slate-200 px-3 py-2 text-center">
|
||||
<div className="text-xs font-extrabold text-slate-700">{(totalElapsed / 1000).toFixed(1)}s</div>
|
||||
<div className="text-[9px] font-bold text-slate-500">总耗时</div>
|
||||
</div>
|
||||
@@ -109,14 +109,14 @@ export function AuditLogViewer({ sessionId, onClose }: AuditLogViewerProps) {
|
||||
{/* 加载/错误状态 */}
|
||||
{loading && (
|
||||
<div className="flex items-center justify-center py-8 text-slate-400 gap-2">
|
||||
<Loader className="w-4 h-4 animate-spin text-sky-600" />
|
||||
<Loader className="w-4 h-4 animate-spin text-blueprint" />
|
||||
<span className="text-xs font-bold">加载审计日志中...</span>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="flex items-center gap-2 text-rose-600 bg-rose-50 border border-rose-200 rounded-lg px-3 py-2 text-xs font-medium">
|
||||
<AlertTriangle className="w-3.5 h-3.5" />
|
||||
{error}
|
||||
<div className="flex items-start gap-2 text-slate-800 bg-slate-50 border border-slate-200 border-l-4 border-l-rose-500 rounded-md px-3 py-2 text-xs font-semibold">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-rose-500 shrink-0 mt-0.2" />
|
||||
<div>{error}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -190,12 +190,12 @@ export function AuditLogViewer({ sessionId, onClose }: AuditLogViewerProps) {
|
||||
<div>
|
||||
{isExpanded ? (
|
||||
<div className="space-y-1">
|
||||
<pre className="font-mono text-[9px] text-slate-600 bg-slate-50 border border-slate-200 rounded p-2 max-h-32 overflow-y-auto whitespace-pre-wrap leading-relaxed">
|
||||
<pre className="font-mono text-[9px] text-slate-600 bg-slate-50 border border-slate-200 rounded-md p-2 max-h-32 overflow-y-auto whitespace-pre-wrap leading-relaxed">
|
||||
{entry.output_preview}
|
||||
</pre>
|
||||
<button
|
||||
onClick={() => setExpandedPreview(prev => ({ ...prev, [entry.id]: false }))}
|
||||
className="text-[9px] font-bold text-sky-600 hover:underline cursor-pointer flex items-center gap-0.5"
|
||||
className="text-[9px] font-bold text-blueprint hover:underline cursor-pointer flex items-center gap-0.5"
|
||||
>
|
||||
<ChevronUp className="w-2.5 h-2.5" />
|
||||
收起
|
||||
@@ -204,7 +204,7 @@ export function AuditLogViewer({ sessionId, onClose }: AuditLogViewerProps) {
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setExpandedPreview(prev => ({ ...prev, [entry.id]: true }))}
|
||||
className="text-left font-mono text-[9px] text-slate-500 hover:text-sky-600 transition-colors cursor-pointer flex items-center gap-0.5 max-w-[200px]"
|
||||
className="text-left font-mono text-[9px] text-slate-500 hover:text-blueprint transition-colors cursor-pointer flex items-center gap-0.5 max-w-[200px]"
|
||||
>
|
||||
<ChevronDown className="w-2.5 h-2.5 shrink-0" />
|
||||
<span className="truncate">{(entry.output_preview ?? '').slice(0, 60)}{(entry.output_preview ?? '').length > 60 ? '...' : ''}</span>
|
||||
|
||||
@@ -88,14 +88,14 @@ export function PermissionRequestCard({ sessionId, onRequestCountChange }: Permi
|
||||
return (
|
||||
<div
|
||||
key={req.permission_id}
|
||||
className="console-panel rounded-xl p-3 shadow-lg border-amber-200/80 bg-amber-50/45 transition-all pointer-events-auto text-xs"
|
||||
className="rounded-md border border-slate-200 border-l-4 border-l-amber-500 bg-slate-50 p-4 transition-all pointer-events-auto text-xs shadow-sm"
|
||||
>
|
||||
{/* 标题与控制栏 */}
|
||||
<div className="mb-2 flex items-center justify-between border-b border-amber-100/40 pb-1.5">
|
||||
<div className="mb-2.5 flex items-center justify-between border-b border-slate-200 pb-2">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<Shield className="h-3.5 w-3.5 text-amber-550 shrink-0" />
|
||||
<span className="font-extrabold text-slate-800 tracking-wide text-[11px] shrink-0">授权确认</span>
|
||||
<code className="rounded bg-amber-100/70 border border-amber-200/80 px-1.5 py-0.2 text-[9px] font-mono font-bold text-amber-800 truncate" title={req.tool_name}>
|
||||
<Shield className="h-3.5 w-3.5 text-amber-500 shrink-0" />
|
||||
<span className="font-bold text-slate-800 tracking-wide text-[11px] shrink-0">敏感操作授权</span>
|
||||
<code className="rounded bg-slate-200/80 border border-slate-300 px-1.5 py-0.5 text-[9px] font-mono font-bold text-slate-700 truncate" title={req.tool_name}>
|
||||
{req.tool_name}
|
||||
</code>
|
||||
</div>
|
||||
@@ -104,7 +104,7 @@ export function PermissionRequestCard({ sessionId, onRequestCountChange }: Permi
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleParams(req.permission_id)}
|
||||
className="text-[10px] font-bold text-sky-600 hover:text-sky-850 cursor-pointer select-none"
|
||||
className="text-[10px] font-bold text-slate-500 hover:text-slate-800 underline underline-offset-2 cursor-pointer select-none"
|
||||
>
|
||||
{showArgs ? '隐藏参数' : '参数详情'}
|
||||
</button>
|
||||
@@ -112,43 +112,43 @@ export function PermissionRequestCard({ sessionId, onRequestCountChange }: Permi
|
||||
</div>
|
||||
|
||||
{/* 警告消息 */}
|
||||
<p className="mb-2 text-[11px] font-semibold text-slate-650 leading-relaxed">{req.message}</p>
|
||||
<p className="mb-3 text-[11px] font-medium text-slate-750 leading-relaxed">{req.message}</p>
|
||||
|
||||
{/* 显示工具参数的简化预览 - 按需展开 */}
|
||||
{hasArgs && showArgs && (
|
||||
<pre className="mb-2.5 max-h-24 overflow-auto rounded-lg bg-slate-50 border border-slate-200/50 p-2 text-[9px] font-mono text-slate-600 scrollbar-thin">
|
||||
<pre className="mb-3 max-h-24 overflow-auto rounded bg-white border border-slate-200 p-2 text-[9px] font-mono text-slate-600 scrollbar-thin">
|
||||
{JSON.stringify(req.arguments, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
|
||||
{/* 操作按钮栏 - 更加紧凑 */}
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => respond(req.tool_call_id, true, false)}
|
||||
disabled={submitting[req.tool_call_id]}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-md text-[10px] font-extrabold transition-all shadow-3xs cursor-pointer bg-emerald-600 hover:bg-emerald-700 text-white disabled:opacity-50 hover:scale-102 active:scale-98"
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-md text-[10px] font-bold transition-all cursor-pointer bg-emerald-700 hover:bg-emerald-800 text-white disabled:opacity-50"
|
||||
>
|
||||
{submitting[req.tool_call_id] ? (
|
||||
<Loader className="h-3 w-3 animate-spin" />
|
||||
) : (
|
||||
<Check className="h-3 w-3" />
|
||||
)}
|
||||
Allow
|
||||
允许一次
|
||||
</button>
|
||||
<button
|
||||
onClick={() => respond(req.tool_call_id, true, true)}
|
||||
disabled={submitting[req.tool_call_id]}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-md text-[10px] font-extrabold transition-all shadow-3xs cursor-pointer bg-sky-600 hover:bg-sky-700 text-white disabled:opacity-50 hover:scale-102 active:scale-98"
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-md text-[10px] font-bold transition-all cursor-pointer bg-slate-800 hover:bg-slate-900 text-white disabled:opacity-50"
|
||||
>
|
||||
Always Allow
|
||||
总是允许
|
||||
</button>
|
||||
<button
|
||||
onClick={() => respond(req.tool_call_id, false, false)}
|
||||
disabled={submitting[req.tool_call_id]}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-md text-[10px] font-extrabold transition-all cursor-pointer bg-red-50 hover:bg-red-100 text-red-700 border border-red-200 hover:border-red-300 disabled:opacity-50 hover:scale-102 active:scale-98"
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-md text-[10px] font-bold transition-all cursor-pointer bg-slate-100 border border-slate-250 text-slate-700 hover:bg-slate-200 disabled:opacity-50"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
Deny
|
||||
拒绝执行
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,10 +45,10 @@ export function SubAgentContainer({
|
||||
<div className="relative space-y-2 select-none">
|
||||
<div
|
||||
className={`absolute -left-[21px] top-3 w-2.5 h-2.5 rounded-full border-2 border-white ${
|
||||
parentStreaming ? 'bg-violet-500 animate-pulse' : 'bg-violet-400'
|
||||
parentStreaming ? 'bg-violet-500' : 'bg-violet-400'
|
||||
}`}
|
||||
/>
|
||||
<div className="border border-violet-200 bg-violet-50/30 rounded-xl shadow-2xs overflow-hidden">
|
||||
<div className="border border-violet-200 bg-violet-50/30 rounded-md shadow-2xs overflow-hidden">
|
||||
{/* 头部 */}
|
||||
<button
|
||||
onClick={onToggle}
|
||||
@@ -57,7 +57,7 @@ export function SubAgentContainer({
|
||||
<div className="flex items-center gap-2">
|
||||
<Network
|
||||
className={`w-4 h-4 ${
|
||||
parentStreaming ? 'text-violet-500 animate-pulse' : 'text-violet-400'
|
||||
parentStreaming ? 'text-violet-500' : 'text-violet-400'
|
||||
}`}
|
||||
/>
|
||||
<span className="text-[10px] font-extrabold text-violet-700 tracking-wider uppercase">
|
||||
|
||||
@@ -38,7 +38,7 @@ export function ThoughtCard({
|
||||
<div
|
||||
className={`absolute -left-[21px] top-3 w-2.5 h-2.5 rounded-full border-2 border-white ${dotColor}`}
|
||||
/>
|
||||
<div className={`space-y-1.5 rounded-xl p-3.5 select-text ${bgClass}`}>
|
||||
<div className={`space-y-1.5 rounded-md p-3 select-text ${bgClass}`}>
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className="w-full flex items-center justify-between text-left cursor-pointer"
|
||||
|
||||
@@ -42,7 +42,7 @@ export function ToolCallCard({
|
||||
<div
|
||||
className={`absolute -left-[21px] top-3 w-2.5 h-2.5 rounded-full border-2 border-white ${dotColor}`}
|
||||
/>
|
||||
<div className="space-y-2 border border-slate-100 bg-white rounded-xl p-3.5 shadow-2xs">
|
||||
<div className="space-y-2 border border-slate-100 bg-white rounded-md p-3 shadow-2xs">
|
||||
{/* 工具名称 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1.5 text-[10px] font-extrabold text-sky-700 tracking-wider uppercase">
|
||||
@@ -64,7 +64,7 @@ export function ToolCallCard({
|
||||
|
||||
{/* 参数 */}
|
||||
{isArgsExpanded && (
|
||||
<div className="font-mono text-[10px] bg-slate-50 border border-slate-200 rounded-lg p-2.5 text-slate-650 overflow-x-auto whitespace-pre-wrap max-w-full leading-relaxed select-text">
|
||||
<div className="font-mono text-[10px] bg-slate-50 border border-slate-200 rounded-md p-2 text-slate-650 overflow-x-auto whitespace-pre-wrap max-w-full leading-relaxed select-text">
|
||||
{JSON.stringify(args, null, 2)}
|
||||
</div>
|
||||
)}
|
||||
@@ -93,7 +93,7 @@ export function ToolCallCard({
|
||||
<div
|
||||
ref={scrollContainerRef}
|
||||
onScroll={handleScroll}
|
||||
className="text-xs bg-slate-50 border border-slate-200 rounded-lg p-3 text-slate-700 overflow-auto select-text leading-relaxed max-h-[60vh] prose prose-sm max-w-none prose-headings:text-slate-800 prose-code:text-sky-700 prose-pre:bg-slate-100 prose-pre:text-xs prose-img:rounded-lg"
|
||||
className="text-xs bg-slate-50 border border-slate-200 rounded-md p-2.5 text-slate-700 overflow-auto select-text leading-relaxed max-h-[60vh] prose prose-sm max-w-none prose-headings:text-slate-800 prose-code:text-sky-700 prose-pre:bg-slate-100 prose-pre:text-xs prose-img:rounded-lg"
|
||||
>
|
||||
<AgentMarkdown>
|
||||
{result!.output}
|
||||
|
||||
@@ -24,13 +24,13 @@ export interface ColorScheme {
|
||||
}
|
||||
|
||||
export const PARENT_COLORS: ColorScheme = {
|
||||
thought: { active: 'bg-purple-400 animate-pulse', idle: 'bg-purple-300' },
|
||||
tool_call: { active: 'bg-sky-500 animate-pulse', idle: 'bg-sky-400' },
|
||||
answer: 'bg-emerald-400',
|
||||
thought: { active: 'bg-slate-400', idle: 'bg-slate-300' },
|
||||
tool_call: { active: 'bg-blueprint', idle: 'bg-slate-300' },
|
||||
answer: 'bg-slate-550',
|
||||
};
|
||||
|
||||
export const SUBAGENT_COLORS: ColorScheme = {
|
||||
thought: { active: 'bg-violet-400 animate-pulse', idle: 'bg-violet-300' },
|
||||
tool_call: { active: 'bg-violet-500 animate-pulse', idle: 'bg-violet-400' },
|
||||
answer: 'bg-violet-400',
|
||||
thought: { active: 'bg-slate-500', idle: 'bg-slate-400' },
|
||||
tool_call: { active: 'bg-blueprint', idle: 'bg-slate-350' },
|
||||
answer: 'bg-slate-450',
|
||||
};
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
|
||||
interface BaseModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
title: React.ReactNode;
|
||||
dotColor?: string; // e.g., 'bg-blueprint', 'bg-rose-600', 'bg-star', etc.
|
||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
children: React.ReactNode;
|
||||
zIndex?: number;
|
||||
}
|
||||
|
||||
export function BaseModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
title,
|
||||
dotColor,
|
||||
size = 'md',
|
||||
children,
|
||||
zIndex = 50,
|
||||
}: BaseModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
const sizeClasses = {
|
||||
sm: 'max-w-sm',
|
||||
md: 'max-w-md',
|
||||
lg: 'max-w-lg',
|
||||
xl: 'max-w-xl',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClose}
|
||||
className="fixed inset-0 flex items-center justify-center bg-slate-900/40 backdrop-blur-xs transition-all animate-fade-in cursor-pointer"
|
||||
style={{ zIndex }}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={`bg-card rounded-lg border border-precision shadow-sm w-full p-6 space-y-4 cursor-default animate-fade-in mx-4 ${sizeClasses[size]}`}
|
||||
>
|
||||
{/* 头部栏 */}
|
||||
<div className="flex items-start justify-between border-b border-slate-150 pb-2.5 shrink-0">
|
||||
<div className="min-w-0 flex-1">
|
||||
{typeof title === 'string' ? (
|
||||
<h3 className="text-xs font-bold text-main flex items-center gap-1.5 pr-4">
|
||||
{dotColor && <span className={`w-1.5 h-1.5 rounded-full shrink-0 ${dotColor}`} />}
|
||||
<span className="truncate">{title}</span>
|
||||
</h3>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-400 hover:text-slate-600 p-1 rounded-md hover:bg-slate-100 transition-colors shrink-0 cursor-pointer"
|
||||
title="关闭"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 内容区 */}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BaseModal } from './BaseModal';
|
||||
|
||||
interface GlobalDialogProps {
|
||||
dialog: {
|
||||
type: 'alert' | 'confirm';
|
||||
@@ -23,49 +25,33 @@ export function GlobalDialog({ dialog, onClose }: GlobalDialogProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleCancel}
|
||||
className="fixed inset-0 z-[60] flex items-center justify-center bg-slate-900/40 backdrop-blur-xs transition-all"
|
||||
<BaseModal
|
||||
isOpen={!!dialog}
|
||||
onClose={handleCancel}
|
||||
title={dialog.title}
|
||||
dotColor={dialog.type === 'confirm' ? 'bg-blueprint' : 'bg-rose-600'}
|
||||
size="sm"
|
||||
zIndex={60}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="bg-[var(--bg-card)] rounded-xl border border-[var(--border-precision)] shadow-md max-w-sm w-full p-6 space-y-4"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-slate-100 pb-2.5">
|
||||
<h3 className="text-xs font-bold text-[var(--text-main)] flex items-center gap-1.5">
|
||||
<span className={`w-2 h-2 rounded-full ${dialog.type === 'confirm' ? 'bg-[var(--accent-blueprint)]' : 'bg-[#ef4444]'} animate-pulse`} />
|
||||
<span>{dialog.title}</span>
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="text-slate-400 hover:text-slate-600 p-1 rounded-lg hover:bg-slate-100 transition-colors cursor-pointer"
|
||||
title="关闭"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-[var(--text-muted)] leading-relaxed whitespace-pre-line">{dialog.message}</p>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button
|
||||
onClick={handleConfirm}
|
||||
className="flex-1 btn-console btn-console-primary py-2 rounded-lg text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
{dialog.type === 'confirm' && (
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="px-4 btn-console btn-console-secondary py-2 rounded-lg text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-muted leading-relaxed whitespace-pre-line font-medium">{dialog.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button
|
||||
onClick={handleConfirm}
|
||||
className="flex-1 btn-console btn-console-primary py-2 rounded-md text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
{dialog.type === 'confirm' && (
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="px-4 btn-console btn-console-secondary py-2 rounded-md text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import axios from 'axios';
|
||||
import { Loader, Download, RefreshCw, AlertTriangle } from 'lucide-react';
|
||||
import type { StandardPaper } from '../../types';
|
||||
import { getDoctypeBadge } from '../../utils/paper';
|
||||
import { BaseModal } from './BaseModal';
|
||||
|
||||
interface PaperDetailModalProps {
|
||||
paper: StandardPaper | null | undefined;
|
||||
@@ -34,130 +35,120 @@ export function PaperDetailModal({
|
||||
}: PaperDetailModalProps) {
|
||||
if (!paper) return null;
|
||||
|
||||
const modalTitle = (
|
||||
<div className="space-y-1 pr-4">
|
||||
<span className="text-[10px] font-bold text-blueprint uppercase tracking-wider block">文献详情元数据</span>
|
||||
<h3 className="text-xs font-bold text-main leading-snug">
|
||||
{getDoctypeBadge(paper.doctype)}
|
||||
<span className="align-middle ml-1">{paper.title}</span>
|
||||
</h3>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClose}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/40 backdrop-blur-xs transition-all animate-fade-in cursor-pointer"
|
||||
<BaseModal
|
||||
isOpen={!!paper}
|
||||
onClose={onClose}
|
||||
title={modalTitle}
|
||||
size="lg"
|
||||
zIndex={50}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="bg-[var(--bg-card)] rounded-xl border border-[var(--border-precision)] shadow-md max-w-lg w-full p-6 space-y-4 cursor-default animate-fade-in"
|
||||
>
|
||||
{/* 标题 & 关闭 */}
|
||||
<div className="flex items-start justify-between border-b border-slate-100 pb-3">
|
||||
<div className="space-y-1 pr-4">
|
||||
<span className="text-[10px] font-bold text-[var(--accent-blueprint)] uppercase tracking-wider">文献详情元数据</span>
|
||||
<h3 className="text-xs font-bold text-[var(--text-main)] leading-snug">
|
||||
{getDoctypeBadge(paper.doctype)}
|
||||
<span className="align-middle ml-1">{paper.title}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-400 hover:text-slate-600 p-1 rounded-lg hover:bg-slate-100 transition-colors shrink-0 cursor-pointer"
|
||||
title="关闭"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 详情内容 */}
|
||||
<div className="space-y-4 h-[460px] flex flex-col overflow-y-auto pr-1 text-xs scrollbar-thin">
|
||||
{/* 作者 */}
|
||||
<div className="space-y-1 h-12 overflow-y-auto scrollbar-thin shrink-0">
|
||||
<span className="text-[var(--text-muted)] font-bold">作者列表</span>
|
||||
<p className="text-[var(--text-main)] leading-relaxed font-semibold">{paper.authors.join(', ')}</p>
|
||||
<span className="text-muted font-bold">作者列表</span>
|
||||
<p className="text-main leading-relaxed font-semibold">{paper.authors.join(', ')}</p>
|
||||
</div>
|
||||
|
||||
{/* 期刊 & 年份 */}
|
||||
<div className="grid grid-cols-5 gap-4 border-y border-slate-100 py-2.5 shrink-0 h-14">
|
||||
<div className="grid grid-cols-5 gap-4 border-y border-slate-150 py-2.5 shrink-0 h-14">
|
||||
<div className="space-y-0.5 flex flex-col justify-center min-w-0 col-span-4">
|
||||
<span className="text-[var(--text-muted)] font-bold block text-[10px] leading-tight">发表期刊</span>
|
||||
<span className="text-muted font-bold block text-[10px] leading-tight">发表期刊</span>
|
||||
<span
|
||||
className="text-[var(--text-main)] font-bold italic truncate block text-[11px] leading-tight"
|
||||
className="text-main font-bold italic truncate block text-[11px] leading-tight"
|
||||
title={paper.pub_journal || '未标注'}
|
||||
>
|
||||
{paper.pub_journal || '未标注'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-0.5 flex flex-col justify-center col-span-1">
|
||||
<span className="text-[var(--text-muted)] font-bold block text-[10px] leading-tight">发表年份</span>
|
||||
<span className="text-[var(--text-main)] font-extrabold block text-[11px] leading-tight">{paper.year}</span>
|
||||
<span className="text-muted font-bold block text-[10px] leading-tight">发表年份</span>
|
||||
<span className="text-main font-extrabold block text-[11px] leading-tight">{paper.year}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 摘要 */}
|
||||
<div className="space-y-1.5 flex flex-col h-40 shrink-0">
|
||||
<span className="text-[var(--text-muted)] font-bold block">摘要</span>
|
||||
<p className="text-[var(--text-main)] leading-relaxed font-normal bg-[#f8fafc] p-3 flex-1 rounded-lg border border-[var(--border-precision)] text-justify overflow-y-auto scrollbar-thin select-text">
|
||||
<span className="text-muted font-bold block">摘要</span>
|
||||
<p className="text-main leading-relaxed font-normal bg-slate-50 p-3 flex-1 rounded-md border border-precision text-justify overflow-y-auto scrollbar-thin select-text">
|
||||
{paper.abstract_text || '该文献暂无摘要数据。'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 关键字 */}
|
||||
{/* 关键词 */}
|
||||
<div className="space-y-1.5 flex flex-col h-16 shrink-0">
|
||||
<span className="text-[var(--text-muted)] font-bold block">关键词</span>
|
||||
<span className="text-muted font-bold block">关键词</span>
|
||||
{paper.keywords && paper.keywords.length > 0 ? (
|
||||
<div className="flex flex-wrap gap-1.5 flex-1 content-start items-start overflow-y-auto scrollbar-thin pr-1">
|
||||
{paper.keywords.map(kw => (
|
||||
<span key={kw} className="px-2 py-0.5 rounded bg-[#f1f5f9] border border-[var(--border-precision)] text-[var(--text-muted)] font-bold text-[9px]">
|
||||
<span key={kw} className="px-2 py-0.5 rounded bg-slate-100 border border-precision text-muted font-bold text-[9px]">
|
||||
{kw}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center flex-1 bg-[#f8fafc] border border-[var(--border-precision)] rounded-lg text-[10px] text-[var(--text-muted)] font-bold select-none">
|
||||
<div className="flex items-center justify-center flex-1 bg-slate-50 border border-precision rounded-md text-[10px] text-muted font-bold select-none">
|
||||
暂无关键词
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 标识符 */}
|
||||
<div className="border-t border-slate-100 pt-3 grid grid-cols-1 sm:grid-cols-3 gap-2 text-[10px] font-mono mt-auto shrink-0">
|
||||
<div className="bg-[#f8fafc] px-2.5 py-1.5 rounded border border-[var(--border-precision)]">
|
||||
<span className="text-[var(--text-muted)] font-bold block">BIBCODE</span>
|
||||
<span className="text-[var(--text-main)] font-semibold select-all truncate block" title={paper.bibcode === paper.arxiv_id ? '暂无' : paper.bibcode}>
|
||||
<div className="border-t border-slate-155 pt-3 grid grid-cols-1 sm:grid-cols-3 gap-2 text-[10px] font-mono mt-auto shrink-0">
|
||||
<div className="bg-slate-50 px-2.5 py-1.5 rounded-md border border-precision">
|
||||
<span className="text-muted font-bold block">BIBCODE</span>
|
||||
<span className="text-main font-semibold select-all truncate block" title={paper.bibcode === paper.arxiv_id ? '暂无' : paper.bibcode}>
|
||||
{paper.bibcode === paper.arxiv_id ? '暂无' : (
|
||||
<a
|
||||
href={`https://ui.adsabs.harvard.edu/abs/${paper.bibcode}/abstract`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => axios.post('/api/active_bibcode', { bibcode: paper.bibcode }).catch(() => {})}
|
||||
className="text-[var(--accent-blueprint)] hover:underline"
|
||||
className="text-blueprint hover:underline"
|
||||
>
|
||||
{paper.bibcode}
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-[#f8fafc] px-2.5 py-1.5 rounded border border-[var(--border-precision)]">
|
||||
<span className="text-[var(--text-muted)] font-bold block">DOI</span>
|
||||
<span className="text-[var(--text-main)] font-semibold select-all truncate block" title={paper.doi || '无'}>
|
||||
<div className="bg-slate-50 px-2.5 py-1.5 rounded-md border border-precision">
|
||||
<span className="text-muted font-bold block">DOI</span>
|
||||
<span className="text-main font-semibold select-all truncate block" title={paper.doi || '无'}>
|
||||
{paper.doi ? (
|
||||
<a
|
||||
href={`https://doi.org/${paper.doi}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => axios.post('/api/active_bibcode', { bibcode: paper.bibcode }).catch(() => {})}
|
||||
className="text-[var(--accent-blueprint)] hover:underline"
|
||||
className="text-blueprint hover:underline"
|
||||
>
|
||||
{paper.doi}
|
||||
</a>
|
||||
) : '无'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="bg-[#f8fafc] px-2.5 py-1.5 rounded border border-[var(--border-precision)]">
|
||||
<span className="text-[var(--text-muted)] font-bold block">ARXIV ID</span>
|
||||
<span className="text-[var(--text-main)] font-semibold select-all truncate block" title={paper.arxiv_id || '无'}>
|
||||
<div className="bg-slate-50 px-2.5 py-1.5 rounded-md border border-precision">
|
||||
<span className="text-muted font-bold block">ARXIV ID</span>
|
||||
<span className="text-main font-semibold select-all truncate block" title={paper.arxiv_id || '无'}>
|
||||
{paper.arxiv_id ? (
|
||||
<a
|
||||
href={`https://arxiv.org/abs/${paper.arxiv_id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
onClick={() => axios.post('/api/active_bibcode', { bibcode: paper.bibcode }).catch(() => {})}
|
||||
className="text-[var(--accent-blueprint)] hover:underline"
|
||||
className="text-blueprint hover:underline"
|
||||
>
|
||||
{paper.arxiv_id}
|
||||
</a>
|
||||
@@ -167,16 +158,16 @@ export function PaperDetailModal({
|
||||
</div>
|
||||
|
||||
{/* 手动上传文件(应对防爬阻断) */}
|
||||
<div className="border-t border-slate-100 pt-3 space-y-2">
|
||||
<div className="border-t border-slate-150 pt-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[var(--text-muted)] font-bold block">手动离线上传文献</span>
|
||||
<span className="text-[9px] text-amber-700 font-bold bg-amber-50 px-2 py-0.5 rounded border border-amber-200">防爬/人机验证备用</span>
|
||||
<span className="text-muted font-bold block">手动离线上传文献</span>
|
||||
<span className="text-[9px] text-amber-800 font-bold bg-amber-50 px-2 py-0.5 rounded-md border border-amber-200/60">防爬/人机验证备用</span>
|
||||
</div>
|
||||
<p className="text-[10px] text-[var(--text-muted)] leading-relaxed">
|
||||
<p className="text-[10px] text-muted leading-relaxed">
|
||||
若自动下载受阻,可在浏览器中打开上方链接,手动保存 PDF 或 HTML 后在此处上传覆盖。
|
||||
</p>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col items-center justify-center border border-dashed border-[var(--border-precision)] rounded-lg p-2 hover:bg-[#f8fafc] transition-colors relative cursor-pointer group min-h-[50px]">
|
||||
<div className="flex flex-col items-center justify-center border border-dashed border-precision rounded-md p-2 hover:bg-slate-50 transition-colors relative cursor-pointer group min-h-[50px]">
|
||||
<input
|
||||
type="file"
|
||||
accept="application/pdf"
|
||||
@@ -188,17 +179,17 @@ export function PaperDetailModal({
|
||||
disabled={uploadingBibcode === paper.bibcode}
|
||||
/>
|
||||
{paper.has_pdf && (
|
||||
<span className="absolute top-1 right-1 text-[8px] font-bold text-emerald-600 bg-emerald-50 border border-emerald-200 px-1 py-0.2 rounded select-none pointer-events-none z-10">
|
||||
<span className="absolute top-1 right-1 text-[8px] font-bold text-emerald-800 bg-emerald-50 border border-emerald-200/60 px-1 py-0.2 rounded-md select-none pointer-events-none z-10">
|
||||
已下载
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[10px] font-bold text-[var(--accent-blueprint)] group-hover:underline">
|
||||
<span className="text-[10px] font-bold text-blueprint group-hover:underline">
|
||||
{uploadingBibcode === paper.bibcode ? '上传中...' : '上传 PDF 文献'}
|
||||
</span>
|
||||
<span className="text-[8px] text-[var(--text-muted)]">支持 .pdf 格式</span>
|
||||
<span className="text-[8px] text-muted">支持 .pdf 格式</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center border border-dashed border-[var(--border-precision)] rounded-lg p-2 hover:bg-[#f8fafc] transition-colors relative cursor-pointer group min-h-[50px]">
|
||||
<div className="flex flex-col items-center justify-center border border-dashed border-precision rounded-md p-2 hover:bg-slate-50 transition-colors relative cursor-pointer group min-h-[50px]">
|
||||
<input
|
||||
type="file"
|
||||
accept="text/html,.html"
|
||||
@@ -210,14 +201,14 @@ export function PaperDetailModal({
|
||||
disabled={uploadingBibcode === paper.bibcode}
|
||||
/>
|
||||
{paper.has_html && (
|
||||
<span className="absolute top-1 right-1 text-[8px] font-bold text-emerald-600 bg-emerald-50 border border-emerald-200 px-1 py-0.2 rounded select-none pointer-events-none z-10">
|
||||
<span className="absolute top-1 right-1 text-[8px] font-bold text-emerald-800 bg-emerald-50 border border-emerald-200/60 px-1 py-0.2 rounded-md select-none pointer-events-none z-10">
|
||||
已下载
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[10px] font-bold text-[var(--accent-blueprint)] group-hover:underline">
|
||||
<span className="text-[10px] font-bold text-blueprint group-hover:underline">
|
||||
{uploadingBibcode === paper.bibcode ? '上传中...' : '上传 HTML 文献'}
|
||||
</span>
|
||||
<span className="text-[8px] text-[var(--text-muted)]">支持 .html 格式</span>
|
||||
<span className="text-[8px] text-muted">支持 .html 格式</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -227,7 +218,7 @@ export function PaperDetailModal({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMarkNoResource(paper.bibcode, true)}
|
||||
className="btn-console btn-console-secondary w-full py-2 rounded-lg text-[10px] font-bold transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||||
className="btn-console btn-console-secondary w-full py-2 rounded-md text-[10px] font-bold transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<RefreshCw className="w-3 h-3" /> 恢复自动下载状态 (允许后续重新尝试)
|
||||
</button>
|
||||
@@ -235,9 +226,9 @@ export function PaperDetailModal({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleMarkNoResource(paper.bibcode, false)}
|
||||
className="btn-console btn-console-secondary w-full py-2 rounded-lg text-[10px] font-bold transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||||
className="btn-console btn-console-secondary w-full py-2 rounded-md text-[10px] font-bold transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<AlertTriangle className="w-3 h-3 text-[var(--accent-star)]" /> 标记为“无有效全文资源” (排查后跳过重试)
|
||||
<AlertTriangle className="w-3 h-3 text-amber-600" /> 标记为“无有效全文资源” (排查后跳过重试)
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -246,34 +237,34 @@ export function PaperDetailModal({
|
||||
|
||||
{/* 自动下载失败诊断 */}
|
||||
{!paper.is_downloaded && (paper.pdf_error || paper.html_error) && (
|
||||
<div className={`rounded-lg p-3 text-[10px] space-y-1 ${
|
||||
<div className={`rounded-md p-3 text-[10px] space-y-1 border border-slate-200 border-l-4 ${
|
||||
(paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource')
|
||||
? 'bg-amber-50 border border-amber-150 text-amber-850'
|
||||
: 'bg-rose-50 border border-rose-150 text-rose-850'
|
||||
? 'bg-slate-50 border-l-amber-500 text-slate-800'
|
||||
: 'bg-slate-50 border-l-rose-500 text-slate-800'
|
||||
}`}>
|
||||
{paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource' ? (
|
||||
<div className="leading-relaxed">
|
||||
<div className="font-bold flex items-center gap-1.5 text-amber-900 text-xs mb-1">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-amber-500 animate-pulse" />
|
||||
<div className="font-bold flex items-center gap-1.5 text-slate-900 text-[11px] mb-1">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-amber-500" />
|
||||
已手动标记为【无有效全文资源】
|
||||
</div>
|
||||
该文献已从物理文献下载重试队列中排除,后续批量下载时系统将自动跳过此文献。
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="font-bold flex items-center gap-1.5 text-rose-900">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-rose-500 animate-pulse" />
|
||||
<div className="font-bold flex items-center gap-1.5 text-slate-900 text-[11px]">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-rose-600" />
|
||||
自动下载失败诊断信息:
|
||||
</div>
|
||||
{paper.pdf_error && (
|
||||
<div className="leading-relaxed">
|
||||
<span className="font-semibold text-rose-750">PDF 错误: </span>
|
||||
<span className="font-semibold text-slate-600">PDF 错误: </span>
|
||||
{paper.pdf_error}
|
||||
</div>
|
||||
)}
|
||||
{paper.html_error && (
|
||||
<div className="leading-relaxed mt-0.5">
|
||||
<span className="font-semibold text-rose-750">HTML 错误: </span>
|
||||
<span className="font-semibold text-slate-600">HTML 错误: </span>
|
||||
{paper.html_error}
|
||||
</div>
|
||||
)}
|
||||
@@ -284,7 +275,7 @@ export function PaperDetailModal({
|
||||
</div>
|
||||
|
||||
{/* 底部操作:整合所有动作(阅读、图谱、下载) */}
|
||||
<div className="flex flex-wrap gap-2 pt-3 border-t border-slate-100">
|
||||
<div className="flex flex-wrap gap-2 pt-3 border-t border-slate-150">
|
||||
{paper.is_downloaded ? (
|
||||
<>
|
||||
<button
|
||||
@@ -292,7 +283,7 @@ export function PaperDetailModal({
|
||||
onClose();
|
||||
openReader(paper);
|
||||
}}
|
||||
className="flex-1 btn-console btn-console-primary py-2.5 rounded-lg text-xs font-bold text-center cursor-pointer"
|
||||
className="flex-1 btn-console btn-console-primary py-2.5 rounded-md text-xs font-bold text-center cursor-pointer"
|
||||
>
|
||||
打开阅读器
|
||||
</button>
|
||||
@@ -303,18 +294,18 @@ export function PaperDetailModal({
|
||||
setActiveTab('citation');
|
||||
loadCitations(paper.bibcode);
|
||||
}}
|
||||
className="flex-1 btn-console btn-console-secondary py-2.5 rounded-lg text-xs font-bold text-center cursor-pointer"
|
||||
className="flex-1 btn-console btn-console-secondary py-2.5 rounded-md text-xs font-bold text-center cursor-pointer"
|
||||
>
|
||||
查看引用图谱
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
showConfirm('确定要强制重新下载吗?这会覆盖本地文件。', () => {
|
||||
showConfirm('确定要强制重新下载吗?这会覆盖本地 file。', () => {
|
||||
handleDownload(paper.bibcode, true);
|
||||
}, '确认重新下载');
|
||||
}}
|
||||
disabled={downloadingBibcodes[paper.bibcode]}
|
||||
className="px-4 btn-console btn-console-secondary text-[var(--accent-star)] py-2.5 rounded-lg text-xs font-bold transition-all cursor-pointer disabled:opacity-50"
|
||||
className="px-4 btn-console btn-console-secondary text-star py-2.5 rounded-md text-xs font-bold transition-all cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
{downloadingBibcodes[paper.bibcode] ? '重下中...' : '重新下载'}
|
||||
</button>
|
||||
@@ -326,7 +317,7 @@ export function PaperDetailModal({
|
||||
handleDownload(paper.bibcode);
|
||||
}}
|
||||
disabled={downloadingBibcodes[paper.bibcode]}
|
||||
className="flex-1 btn-console btn-console-primary py-2.5 rounded-lg text-xs font-bold flex items-center justify-center gap-1.5 cursor-pointer disabled:opacity-50"
|
||||
className="flex-1 btn-console btn-console-primary py-2.5 rounded-md text-xs font-bold flex items-center justify-center gap-1.5 cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
{downloadingBibcodes[paper.bibcode] ? (
|
||||
<>
|
||||
@@ -347,14 +338,13 @@ export function PaperDetailModal({
|
||||
setActiveTab('citation');
|
||||
loadCitations(paper.bibcode);
|
||||
}}
|
||||
className="px-6 btn-console btn-console-secondary py-2.5 rounded-lg text-xs font-bold text-center cursor-pointer"
|
||||
className="px-6 btn-console btn-console-secondary py-2.5 rounded-md text-xs font-bold text-center cursor-pointer"
|
||||
>
|
||||
引用图谱
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { BaseModal } from './BaseModal';
|
||||
|
||||
interface UncachedPaperModalProps {
|
||||
bibcode: string | null;
|
||||
@@ -10,64 +11,48 @@ export function UncachedPaperModal({ bibcode, onClose, loadCitations }: Uncached
|
||||
if (!bibcode) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClose}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/40 backdrop-blur-xs transition-all animate-fade-in"
|
||||
<BaseModal
|
||||
isOpen={!!bibcode}
|
||||
onClose={onClose}
|
||||
title="文献尚未入库"
|
||||
dotColor="bg-star"
|
||||
size="sm"
|
||||
zIndex={50}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="bg-[var(--bg-card)] rounded-xl border border-[var(--border-precision)] shadow-md max-w-sm w-full p-6 space-y-4"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-slate-100 pb-2.5">
|
||||
<h3 className="text-xs font-bold text-[var(--text-main)] flex items-center gap-1.5">
|
||||
<span className="w-2 h-2 rounded-full bg-[var(--accent-star)] animate-pulse" />
|
||||
<span>文献尚未入库</span>
|
||||
</h3>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-400 hover:text-slate-600 p-1 rounded-lg hover:bg-slate-100 transition-colors cursor-pointer"
|
||||
title="关闭"
|
||||
>
|
||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-[var(--text-main)] leading-relaxed">
|
||||
文献 <span className="font-mono bg-slate-100 px-1.5 py-0.5 rounded text-[var(--accent-blueprint)] font-bold select-all">{bibcode}</span> 尚未收录在本地数据库中。
|
||||
</p>
|
||||
<p className="text-[11px] text-[var(--text-muted)] leading-relaxed">
|
||||
您可以选择在线拉取该文献元数据并入库,或是直接跳转至 NASA ADS 平台查看其原始页面。
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
loadCitations(bibcode, false);
|
||||
onClose();
|
||||
}}
|
||||
className="flex-1 btn-console btn-console-primary py-2 rounded-lg text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
拉取入库并展开
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
axios.post('/api/active_bibcode', { bibcode }).catch(() => {});
|
||||
window.open(`https://ui.adsabs.harvard.edu/abs/${bibcode}/abstract`, '_blank');
|
||||
}}
|
||||
className="flex-1 btn-console btn-console-secondary py-2 rounded-lg text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
跳转到 ADS
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-3 btn-console btn-console-secondary py-2 rounded-lg text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-main leading-relaxed">
|
||||
文献 <span className="font-mono bg-slate-100 px-1.5 py-0.5 rounded-md text-blueprint font-bold select-all">{bibcode}</span> 尚未收录在本地数据库中。
|
||||
</p>
|
||||
<p className="text-[11px] text-muted leading-relaxed">
|
||||
您可以选择在线拉取该文献元数据并入库,或是直接跳转至 NASA ADS 平台查看其原始页面。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
loadCitations(bibcode, false);
|
||||
onClose();
|
||||
}}
|
||||
className="flex-1 btn-console btn-console-primary py-2 rounded-md text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
拉取入库并展开
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
axios.post('/api/active_bibcode', { bibcode }).catch(() => {});
|
||||
window.open(`https://ui.adsabs.harvard.edu/abs/${bibcode}/abstract`, '_blank');
|
||||
}}
|
||||
className="flex-1 btn-console btn-console-secondary py-2 rounded-md text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
跳转到 ADS
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-3 btn-console btn-console-secondary py-2 rounded-md text-[11px] font-bold text-center cursor-pointer"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ export function Sidebar({ activeTab, setActiveTab, selectedPaper, loadCitations,
|
||||
type="button"
|
||||
disabled={!isCollapsed}
|
||||
onClick={() => setIsCollapsed(false)}
|
||||
className={`flex items-center justify-center shrink-0 rounded-xl transition-all duration-300 ${
|
||||
className={`flex items-center justify-center shrink-0 rounded-lg 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-11 h-11 bg-white hover:bg-slate-50 border border-slate-200 cursor-pointer shadow-xs hover:shadow-sm'
|
||||
: 'w-9 h-9 bg-transparent border border-transparent cursor-default'
|
||||
}`}
|
||||
title={isCollapsed ? "展开导航" : undefined}
|
||||
@@ -116,13 +116,13 @@ export function Sidebar({ activeTab, setActiveTab, selectedPaper, loadCitations,
|
||||
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'
|
||||
? 'bg-slate-100 border-slate-200 text-slate-850 shadow-xs'
|
||||
: 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'}`} />
|
||||
<Icon className={`w-4 h-4 shrink-0 transition-colors duration-300 ${isActive ? 'text-slate-700' : 'text-slate-500'}`} />
|
||||
<span
|
||||
className={`truncate transition-all duration-300 origin-left ${
|
||||
isCollapsed
|
||||
@@ -145,13 +145,13 @@ export function Sidebar({ activeTab, setActiveTab, selectedPaper, loadCitations,
|
||||
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'
|
||||
: 'p-3.5 border-slate-200 bg-slate-100/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 ${
|
||||
className={`transition-all duration-300 flex items-center justify-center rounded-lg border border-slate-200 bg-slate-50 text-slate-650 shrink-0 shadow-xs ${
|
||||
isCollapsed
|
||||
? 'w-9 h-9 opacity-100 scale-100'
|
||||
: 'w-0 h-0 opacity-0 scale-75 overflow-hidden'
|
||||
@@ -168,7 +168,7 @@ export function Sidebar({ activeTab, setActiveTab, selectedPaper, loadCitations,
|
||||
: 'opacity-100 max-w-[200px] max-h-40'
|
||||
}`}
|
||||
>
|
||||
<span className="text-[9px] font-bold text-sky-600 tracking-widest block mb-1">当前选定文献</span>
|
||||
<span className="text-[9px] font-bold text-slate-500 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>
|
||||
|
||||
@@ -48,13 +48,13 @@ function PaperMetadataDossier({ metadata, isChinese }: { metadata: PaperMetadata
|
||||
if (!metadata || (!metadata.title && !metadata.author)) return null;
|
||||
|
||||
return (
|
||||
<div className="mb-6 rounded-xl p-5 bg-gradient-to-br from-slate-50 to-slate-100 border border-slate-200 shadow-xs relative overflow-hidden select-text">
|
||||
{/* 科技风背景网格点缀 */}
|
||||
<div className="absolute top-0 right-0 w-24 h-24 bg-sky-550/5 rounded-bl-full pointer-events-none border-l border-b border-sky-500/10" />
|
||||
<div className="mb-6 rounded-lg p-5 bg-slate-50 border border-slate-200 relative overflow-hidden select-text">
|
||||
{/* 科技风背景线段装饰 - 极细学术边框线 */}
|
||||
<div className="absolute top-0 right-0 w-16 h-16 bg-slate-100/50 pointer-events-none border-l border-b border-slate-200/60" />
|
||||
|
||||
{/* 顶部档案标徽 */}
|
||||
<div className="flex items-center gap-2 mb-3 select-none">
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-[9px] font-extrabold bg-sky-600 text-white uppercase tracking-wider">
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-[9px] font-bold bg-slate-700 text-white uppercase tracking-wider">
|
||||
{isChinese ? '文献元数据档案' : 'Paper Metadata Dossier'}
|
||||
</span>
|
||||
{metadata.date && (
|
||||
@@ -160,7 +160,7 @@ export function BilingualViewer({
|
||||
<div
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseLeave={handleContainerMouseLeave}
|
||||
className="console-panel rounded-xl p-6 bg-white border border-slate-200 relative flex flex-col min-h-0 overflow-hidden"
|
||||
className="console-panel rounded-lg p-6 bg-white border border-slate-200 relative flex flex-col min-h-0 overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4 border-b border-slate-100 pb-2.5 shrink-0 select-none">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -169,7 +169,7 @@ export function BilingualViewer({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPdf(!showPdf)}
|
||||
className="text-[9px] font-bold px-2 py-0.5 rounded bg-sky-50 text-sky-700 border border-sky-200 hover:bg-sky-100 transition-all cursor-pointer flex items-center gap-1"
|
||||
className="text-[9px] font-bold px-2 py-0.5 rounded bg-slate-50 text-blueprint border border-slate-200 hover:bg-slate-100 transition-all cursor-pointer flex items-center gap-1"
|
||||
>
|
||||
{showPdf ? (
|
||||
<>
|
||||
@@ -266,7 +266,7 @@ export function BilingualViewer({
|
||||
{/* 中文翻译视窗 (或双语对照) */}
|
||||
{(viewMode === 'chinese' || viewMode === 'bilingual') && (
|
||||
<div
|
||||
className="console-panel rounded-xl p-6 bg-white border border-slate-200 relative flex flex-col overflow-hidden min-h-0"
|
||||
className="console-panel rounded-lg p-6 bg-white border border-slate-200 relative flex flex-col overflow-hidden min-h-0"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4 border-b border-slate-100 pb-2.5 shrink-0 select-none">
|
||||
<span className="text-xs font-bold text-slate-800">中文学术对比翻译</span>
|
||||
@@ -274,7 +274,7 @@ export function BilingualViewer({
|
||||
|
||||
{translating ? (
|
||||
<div className="flex-1 flex flex-col items-center justify-center text-slate-500 space-y-3">
|
||||
<Loader className="w-8 h-8 animate-spin text-sky-600" />
|
||||
<Loader className="w-8 h-8 animate-spin text-blueprint" />
|
||||
<p className="text-xs font-bold text-center">天文学专属词典加载中,正在通过大模型进行术语修正翻译...</p>
|
||||
</div>
|
||||
) : chnPure ? (
|
||||
@@ -327,37 +327,36 @@ export function BilingualViewer({
|
||||
transform: hoverCardPos.isAbove ? 'translateY(-100%)' : 'none',
|
||||
zIndex: 9999,
|
||||
}}
|
||||
className="console-panel rounded-xl p-4 bg-white border border-slate-200 shadow-md w-72 text-xs space-y-2 pointer-events-auto select-text animate-in fade-in duration-200"
|
||||
className="bg-white rounded-lg p-4 border border-slate-350 shadow-sm w-72 text-[11px] space-y-2 pointer-events-auto select-text animate-in fade-in duration-200"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-slate-100 pb-1.5">
|
||||
<span className="font-bold text-slate-950 text-sm">{hoveredTarget.target_name}</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center justify-between border-b border-slate-200 pb-2">
|
||||
<span className="font-bold text-slate-900 text-[13px]">{hoveredTarget.target_name}</span>
|
||||
<div className="flex items-center gap-1.5 select-none">
|
||||
<a
|
||||
href={`https://simbad.cds.unistra.fr/simbad/sim-id?Ident=${encodeURIComponent(hoveredTarget.target_name)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[10px] font-bold text-sky-600 hover:text-sky-850 hover:underline cursor-pointer"
|
||||
className="text-[9px] font-extrabold bg-slate-100 hover:bg-slate-200 text-slate-700 hover:text-slate-900 px-1.5 py-0.5 rounded border border-slate-200 transition-all cursor-pointer"
|
||||
title="在 SIMBAD 中查询该天体详情"
|
||||
>
|
||||
SIMBAD
|
||||
</a>
|
||||
<span className="text-[9px] text-slate-300">|</span>
|
||||
<a
|
||||
href={`https://vizier.cds.unistra.fr/viz-bin/VizieR?-c=${encodeURIComponent(hoveredTarget.target_name)}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[10px] font-bold text-sky-600 hover:text-sky-850 hover:underline cursor-pointer"
|
||||
className="text-[9px] font-extrabold bg-slate-100 hover:bg-slate-200 text-slate-700 hover:text-slate-900 px-1.5 py-0.5 rounded border border-slate-200 transition-all cursor-pointer"
|
||||
title="在 VizieR 中查询相关文献和表数据"
|
||||
>
|
||||
VizieR
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-x-2 gap-y-1.5 text-slate-500">
|
||||
<div className="grid grid-cols-2 gap-x-2 gap-y-1.5 text-slate-600">
|
||||
{/* 天体类型 & 官方名称 */}
|
||||
<div className="col-span-2 flex items-center gap-2">
|
||||
{hoveredTarget.otype && (
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded bg-amber-50 text-amber-700 border border-amber-200 text-[10px] font-bold font-mono">
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded bg-amber-50 text-amber-850 border border-amber-200/70 text-[9px] font-bold font-mono">
|
||||
{hoveredTarget.otype}
|
||||
</span>
|
||||
)}
|
||||
@@ -369,20 +368,20 @@ export function BilingualViewer({
|
||||
</div>
|
||||
{/* 坐标 */}
|
||||
<div>
|
||||
<span className="text-slate-400 font-semibold">RA (J2000):</span>
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block">RA (J2000)</span>
|
||||
<div className="font-mono font-bold text-slate-800">{hoveredTarget.ra || '未知'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-slate-400 font-semibold">Dec (J2000):</span>
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block">Dec (J2000)</span>
|
||||
<div className="font-mono font-bold text-slate-800">{hoveredTarget.dec || '未知'}</div>
|
||||
</div>
|
||||
{/* 光谱型 & 视星等 */}
|
||||
<div>
|
||||
<span className="text-slate-400 font-semibold">光谱型:</span>
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block">光谱型</span>
|
||||
<div className="font-bold text-slate-800">{hoveredTarget.spectral_type || '未知'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-slate-400 font-semibold">视星等 (V):</span>
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block">视星等 (V)</span>
|
||||
<div className="font-bold text-slate-800">
|
||||
{hoveredTarget.v_magnitude !== null && hoveredTarget.v_magnitude !== undefined
|
||||
? `${hoveredTarget.v_magnitude.toFixed(2)}`
|
||||
@@ -391,7 +390,7 @@ export function BilingualViewer({
|
||||
</div>
|
||||
{/* 视差 + 误差 + 估算距离 */}
|
||||
<div className="col-span-2">
|
||||
<span className="text-slate-400 font-semibold">视差 / 估算距离:</span>
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block">视差 / 估算距离</span>
|
||||
<div className="font-bold text-slate-800">
|
||||
{hoveredTarget.parallax !== null && hoveredTarget.parallax !== undefined
|
||||
? `${hoveredTarget.parallax.toFixed(2)}${hoveredTarget.parallax_err != null ? `±${hoveredTarget.parallax_err.toFixed(4)}` : ''} mas (~${(1000.0 / hoveredTarget.parallax).toFixed(1)} pc)`
|
||||
@@ -400,8 +399,8 @@ export function BilingualViewer({
|
||||
</div>
|
||||
{/* 自行 */}
|
||||
<div className="col-span-2">
|
||||
<span className="text-slate-400 font-semibold">自行:</span>
|
||||
<div className="font-bold text-slate-800">
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block">自行</span>
|
||||
<div className="font-bold text-slate-800 text-[10.5px]">
|
||||
{hoveredTarget.pm_ra != null && hoveredTarget.pm_de != null
|
||||
? `RA ${hoveredTarget.pm_ra.toFixed(3)}, Dec ${hoveredTarget.pm_de.toFixed(3)} mas/yr`
|
||||
: '未知'}
|
||||
@@ -409,7 +408,7 @@ export function BilingualViewer({
|
||||
</div>
|
||||
{/* 视向速度 */}
|
||||
<div className="col-span-2">
|
||||
<span className="text-slate-400 font-semibold">视向速度:</span>
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block">视向速度</span>
|
||||
<div className="font-bold text-slate-800">
|
||||
{hoveredTarget.radial_velocity != null
|
||||
? `${hoveredTarget.radial_velocity.toFixed(1)} km/s`
|
||||
@@ -419,9 +418,9 @@ export function BilingualViewer({
|
||||
</div>
|
||||
{/* 多波段测光 */}
|
||||
{hoveredTarget.photometry && Object.keys(hoveredTarget.photometry).length > 1 && (
|
||||
<div className="border-t border-slate-100 pt-1.5">
|
||||
<span className="text-slate-400 font-semibold block mb-1">多波段星等:</span>
|
||||
<div className="flex flex-wrap gap-x-2 gap-y-0.5 text-[10px] font-mono text-slate-600">
|
||||
<div className="border-t border-slate-200/60 pt-2">
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block mb-1">多波段星等</span>
|
||||
<div className="flex flex-wrap gap-x-2.5 gap-y-0.5 text-[10px] font-mono text-slate-600">
|
||||
{Object.entries(hoveredTarget.photometry).map(([band, mag]) => (
|
||||
<span key={band} className="whitespace-nowrap">
|
||||
<span className="text-slate-400">{band}</span>{' '}
|
||||
@@ -432,8 +431,8 @@ export function BilingualViewer({
|
||||
</div>
|
||||
)}
|
||||
{hoveredTarget.aliases && hoveredTarget.aliases.length > 0 && (
|
||||
<div className="border-t border-slate-100 pt-1.5">
|
||||
<span className="text-slate-400 font-semibold block mb-0.5">常用别名:</span>
|
||||
<div className="border-t border-slate-200/60 pt-2">
|
||||
<span className="text-slate-500 font-bold tracking-wide text-[9px] uppercase block mb-0.5">常用别名</span>
|
||||
<div className="text-[10px] text-slate-500 font-medium leading-relaxed max-h-[36px] overflow-y-auto scrollbar-thin font-mono">
|
||||
{hoveredTarget.aliases.slice(0, 8).join(', ')}
|
||||
{hoveredTarget.aliases.length > 8 && ' ...'}
|
||||
|
||||
@@ -69,7 +69,7 @@ export function ReaderNotesSidebar({
|
||||
if (!showNotesPanel) return null;
|
||||
|
||||
return (
|
||||
<div className="console-panel rounded-xl border border-slate-200 bg-slate-50 flex flex-col overflow-hidden relative shadow-sm h-full">
|
||||
<div className="console-panel rounded-lg border border-slate-200 bg-slate-50 flex flex-col overflow-hidden relative shadow-sm h-full">
|
||||
<div className="px-4 py-3 border-b border-slate-200 flex items-center justify-between bg-white shrink-0">
|
||||
<span className="text-xs font-bold text-slate-800">
|
||||
{sidebarTab === 'notes' ? '观测记录手札' : '文献 AI 问答'}
|
||||
@@ -86,7 +86,7 @@ export function ReaderNotesSidebar({
|
||||
onClick={() => setSidebarTab('notes')}
|
||||
className={`flex-1 py-2 text-center text-xs font-bold transition-all border-b-2 cursor-pointer ${
|
||||
sidebarTab === 'notes'
|
||||
? 'border-sky-600 text-sky-700'
|
||||
? 'border-blueprint text-blueprint'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
@@ -97,7 +97,7 @@ export function ReaderNotesSidebar({
|
||||
onClick={() => setSidebarTab('ai')}
|
||||
className={`flex-1 py-2 text-center text-xs font-bold transition-all border-b-2 cursor-pointer ${
|
||||
sidebarTab === 'ai'
|
||||
? 'border-sky-600 text-sky-700'
|
||||
? 'border-blueprint text-blueprint'
|
||||
: 'border-transparent text-slate-500 hover:text-slate-700'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -84,7 +84,7 @@ export function ReaderToolbar({
|
||||
{showSwitchMenu && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-45" onClick={() => setShowSwitchMenu(false)} />
|
||||
<div className="absolute right-0 mt-1.5 w-72 rounded-xl bg-white border border-slate-200 shadow-xl py-1.5 z-50 text-xs max-h-96 overflow-y-auto scrollbar-thin">
|
||||
<div className="absolute right-0 mt-1.5 w-72 rounded-md bg-white border border-slate-200 shadow-md py-1.5 z-50 text-xs max-h-96 overflow-y-auto scrollbar-thin">
|
||||
{/* 最近阅读部分 */}
|
||||
{recentlySelected.length > 0 && (
|
||||
<div className="border-b border-slate-100 pb-1.5 mb-1.5">
|
||||
@@ -101,7 +101,7 @@ export function ReaderToolbar({
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left hover:bg-slate-50 transition-colors flex flex-col gap-0.5 border-l-2 ${
|
||||
paper.bibcode === selectedPaper.bibcode
|
||||
? 'border-sky-500 bg-sky-50/30 text-sky-850 font-bold'
|
||||
? 'border-blueprint bg-blueprint/5 text-blueprint font-bold'
|
||||
: 'border-transparent text-slate-700 font-medium'
|
||||
}`}
|
||||
>
|
||||
@@ -132,7 +132,7 @@ export function ReaderToolbar({
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left hover:bg-slate-50 transition-colors flex flex-col gap-0.5 border-l-2 ${
|
||||
paper.bibcode === selectedPaper.bibcode
|
||||
? 'border-sky-500 bg-sky-50/30 text-sky-850 font-bold'
|
||||
? 'border-blueprint bg-blueprint/5 text-blueprint font-bold'
|
||||
: 'border-transparent text-slate-700 font-medium'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -48,10 +48,10 @@ export function BatchPipelineCard({
|
||||
handleStopBatch,
|
||||
}: BatchPipelineCardProps) {
|
||||
return (
|
||||
<div className="console-panel p-6 rounded-xl border border-slate-200 bg-white space-y-6 relative overflow-hidden shadow-xs">
|
||||
<div className="console-panel p-6 rounded-lg border border-slate-200 bg-white space-y-6 relative overflow-hidden shadow-sm">
|
||||
<div className="flex flex-col gap-1 select-none">
|
||||
<h3 className="text-xs font-bold text-slate-900 flex items-center gap-2">
|
||||
<Download className="w-4 h-4 text-sky-600" />
|
||||
<Download className="w-4 h-4 text-slate-700" />
|
||||
<span>馆藏文献批量学术流水线任务</span>
|
||||
</h3>
|
||||
<p className="text-slate-500 text-xs mt-0.5">
|
||||
@@ -60,7 +60,7 @@ export function BatchPipelineCard({
|
||||
</div>
|
||||
|
||||
{batchError && (
|
||||
<div className="p-4 rounded-lg bg-red-50 border border-red-200 flex gap-3 text-xs text-red-750 items-start">
|
||||
<div className="p-4 rounded-md bg-rose-50 border border-rose-200/60 flex gap-3 text-xs text-rose-800 items-start">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" />
|
||||
<div>{batchError}</div>
|
||||
</div>
|
||||
@@ -92,7 +92,7 @@ export function BatchPipelineCard({
|
||||
value={batchLimitCount}
|
||||
disabled={batchStatus.active}
|
||||
onChange={e => setBatchLimitCount(Math.max(1, parseInt(e.target.value) || 0))}
|
||||
className="w-full px-3 py-2 rounded-lg bg-slate-50 border border-slate-300 text-slate-900 focus:outline-none focus:border-sky-500 transition-all text-xs font-medium"
|
||||
className="w-full px-3 py-2 rounded-md bg-slate-50 border border-slate-300 text-slate-900 focus:outline-none focus:border-blueprint transition-all text-xs font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -122,7 +122,7 @@ export function BatchPipelineCard({
|
||||
checked={skipCompleted}
|
||||
disabled={batchStatus.active}
|
||||
onChange={e => setSkipCompleted(e.target.checked)}
|
||||
className="rounded text-sky-600 focus:ring-sky-500"
|
||||
className="rounded text-blueprint focus:ring-blueprint/25"
|
||||
/>
|
||||
<span>跳过已完成</span>
|
||||
</label>
|
||||
@@ -133,7 +133,7 @@ export function BatchPipelineCard({
|
||||
checked={skipFailed}
|
||||
disabled={batchStatus.active}
|
||||
onChange={e => setSkipFailed(e.target.checked)}
|
||||
className="rounded text-sky-600 focus:ring-sky-500"
|
||||
className="rounded text-blueprint focus:ring-blueprint/25"
|
||||
/>
|
||||
<span>跳过当前失败</span>
|
||||
</label>
|
||||
@@ -144,7 +144,7 @@ export function BatchPipelineCard({
|
||||
checked={skipPrecedingFailed}
|
||||
disabled={batchStatus.active}
|
||||
onChange={e => setSkipPrecedingFailed(e.target.checked)}
|
||||
className="rounded text-sky-600 focus:ring-sky-500"
|
||||
className="rounded text-blueprint focus:ring-blueprint/25"
|
||||
/>
|
||||
<span>跳过前置失败</span>
|
||||
</label>
|
||||
@@ -155,7 +155,7 @@ export function BatchPipelineCard({
|
||||
checked={skipPrecedingUncompleted}
|
||||
disabled={batchStatus.active}
|
||||
onChange={e => setSkipPrecedingUncompleted(e.target.checked)}
|
||||
className="rounded text-sky-600 focus:ring-sky-500"
|
||||
className="rounded text-blueprint focus:ring-blueprint/25"
|
||||
/>
|
||||
<span>跳过前置未完成</span>
|
||||
</label>
|
||||
@@ -169,7 +169,7 @@ export function BatchPipelineCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleStopBatch}
|
||||
className="w-full py-2 rounded-lg bg-red-600 hover:bg-red-700 text-white text-xs font-bold flex items-center justify-center gap-2 transition-all shadow-sm cursor-pointer"
|
||||
className="w-full py-2 rounded-md bg-rose-750 hover:bg-rose-855 text-white text-xs font-bold flex items-center justify-center gap-2 transition-all shadow-sm cursor-pointer"
|
||||
>
|
||||
<StopCircle className="w-3.5 h-3.5" />
|
||||
停止批量任务
|
||||
@@ -178,7 +178,7 @@ export function BatchPipelineCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleStartBatch}
|
||||
className="btn-console btn-console-primary w-full py-2 rounded-lg text-xs font-bold flex items-center justify-center gap-2 cursor-pointer"
|
||||
className="btn-console btn-console-primary w-full py-2 rounded-md text-xs font-bold flex items-center justify-center gap-2 cursor-pointer"
|
||||
>
|
||||
<Play className="w-3.5 h-3.5" />
|
||||
启动批量任务
|
||||
@@ -193,11 +193,11 @@ export function BatchPipelineCard({
|
||||
<div className="space-y-1.5">
|
||||
<div className="flex justify-between text-xs font-bold text-slate-600 select-none">
|
||||
<span className="flex items-center gap-1.5">
|
||||
{batchStatus.action === 'download' && <Download className="w-3.5 h-3.5 text-sky-600" />}
|
||||
{batchStatus.action === 'parse' && <FileText className="w-3.5 h-3.5 text-emerald-650" />}
|
||||
{batchStatus.action === 'translate' && <RefreshCw className="w-3.5 h-3.5 text-indigo-600 animate-spin" />}
|
||||
{batchStatus.action === 'download' && <Download className="w-3.5 h-3.5 text-blueprint" />}
|
||||
{batchStatus.action === 'parse' && <FileText className="w-3.5 h-3.5 text-emerald-700" />}
|
||||
{batchStatus.action === 'translate' && <RefreshCw className="w-3.5 h-3.5 text-slate-700 animate-spin" />}
|
||||
{batchStatus.action === 'embed' && <SlidersHorizontal className="w-3.5 h-3.5 text-amber-600" />}
|
||||
{batchStatus.action === 'target' && <RefreshCw className="w-3.5 h-3.5 text-sky-600" />}
|
||||
{batchStatus.action === 'target' && <RefreshCw className="w-3.5 h-3.5 text-blueprint" />}
|
||||
{batchStatus.action === 'download'
|
||||
? '正文离线下载进度'
|
||||
: batchStatus.action === 'parse'
|
||||
@@ -218,18 +218,18 @@ export function BatchPipelineCard({
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-2 rounded-full bg-slate-100 overflow-hidden border border-slate-200 select-none">
|
||||
<div className="w-full h-2 rounded-md bg-slate-100 overflow-hidden border border-slate-200 select-none">
|
||||
<div
|
||||
className={`h-full transition-all duration-300 ${
|
||||
batchStatus.action === 'download'
|
||||
? 'bg-sky-600'
|
||||
? 'bg-blueprint'
|
||||
: batchStatus.action === 'parse'
|
||||
? 'bg-emerald-600'
|
||||
: batchStatus.action === 'translate'
|
||||
? 'bg-indigo-600'
|
||||
? 'bg-slate-700'
|
||||
: batchStatus.action === 'embed'
|
||||
? 'bg-amber-600'
|
||||
: 'bg-sky-600'
|
||||
: 'bg-blueprint'
|
||||
}`}
|
||||
style={{
|
||||
width: `${
|
||||
@@ -253,15 +253,15 @@ export function BatchPipelineCard({
|
||||
|
||||
{batchStatus.active && batchStatus.current_bibcode && (
|
||||
<div className="text-xs font-bold text-slate-600 flex items-center gap-2">
|
||||
<Loader className="w-3.5 h-3.5 text-sky-600 animate-spin" />
|
||||
<span>当前正在处理: <code className="bg-slate-100 px-2 py-0.5 rounded font-mono font-bold text-slate-800 border border-slate-200 select-all">{batchStatus.current_bibcode}</code></span>
|
||||
<Loader className="w-3.5 h-3.5 text-blueprint animate-spin" />
|
||||
<span>当前正在处理: <code className="bg-slate-100 px-2 py-0.5 rounded-md font-mono font-bold text-slate-800 border border-slate-200 select-all">{batchStatus.current_bibcode}</code></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 滚动日志终端 */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-bold text-slate-700 block select-none">实时处理日志流终端</label>
|
||||
<div ref={logsContainerRef} className="bg-slate-50 text-slate-800 font-mono text-[11px] p-4 rounded-lg h-48 overflow-y-auto border border-slate-250 space-y-1 scrollbar-thin scrollbar-thumb-slate-300 relative select-all">
|
||||
<div ref={logsContainerRef} className="bg-slate-50 text-slate-800 font-mono text-[11px] p-4 rounded-md h-48 overflow-y-auto border border-slate-250 space-y-1 scrollbar-thin scrollbar-thumb-slate-300 relative select-all">
|
||||
{batchStatus.logs.length === 0 ? (
|
||||
<div className="text-slate-400 italic select-none">等待数据流任务启动,暂无日志输出...</div>
|
||||
) : (
|
||||
|
||||
@@ -19,10 +19,10 @@ export function BookmarkletToolCard() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="console-panel p-6 rounded-xl border border-slate-200 bg-white space-y-4 shadow-xs">
|
||||
<div className="console-panel p-6 rounded-lg border border-slate-200 bg-white space-y-4 shadow-sm">
|
||||
<div className="flex flex-col gap-1 border-b border-slate-100 pb-2 select-none">
|
||||
<h3 className="text-xs font-bold text-slate-900 flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-sky-500 animate-pulse" />
|
||||
<span className="w-2 h-2 rounded-full bg-blueprint" />
|
||||
<span>浏览器快捷直推书签导入工具</span>
|
||||
</h3>
|
||||
<p className="text-slate-500 text-xs mt-1 leading-relaxed">
|
||||
@@ -41,7 +41,7 @@ export function BookmarkletToolCard() {
|
||||
{/* 可直接拖拽的链接按钮 */}
|
||||
<a
|
||||
ref={linkRef}
|
||||
className="px-4 py-2 bg-gradient-to-r from-sky-500 to-indigo-500 hover:from-sky-650 hover:to-indigo-650 text-white rounded-lg text-xs font-bold transition-all shadow-sm cursor-move flex items-center gap-1.5"
|
||||
className="px-4 py-2 bg-blueprint hover:bg-slate-800 text-white rounded-md text-xs font-bold transition-all shadow-2xs cursor-move flex items-center gap-1.5"
|
||||
title="拖拽我到您的书签栏中"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
@@ -53,7 +53,7 @@ export function BookmarkletToolCard() {
|
||||
|
||||
<button
|
||||
onClick={handleCopyCode}
|
||||
className="px-3 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-lg text-[11px] font-bold transition-all cursor-pointer"
|
||||
className="px-3 py-2 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-md text-[11px] font-bold transition-all cursor-pointer"
|
||||
>
|
||||
复制 JavaScript 书签代码
|
||||
</button>
|
||||
|
||||
@@ -48,7 +48,7 @@ export function MetadataSyncCard({
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 控制面板卡片 */}
|
||||
<div className="console-panel p-6 rounded-xl space-y-6 relative overflow-hidden bg-white border border-slate-200">
|
||||
<div className="console-panel p-6 rounded-lg space-y-6 relative overflow-hidden bg-white border border-slate-200">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-bold text-slate-700 block flex justify-between items-center">
|
||||
@@ -56,7 +56,7 @@ export function MetadataSyncCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowBuilder(!showBuilder)}
|
||||
className="text-xs text-sky-600 hover:text-sky-750 font-bold flex items-center gap-1 cursor-pointer"
|
||||
className="text-xs text-slate-655 hover:text-slate-850 font-bold flex items-center gap-1 cursor-pointer underline underline-offset-2"
|
||||
>
|
||||
<SlidersHorizontal className="w-3.5 h-3.5" />
|
||||
{showBuilder ? '隐藏高级构造' : '高级检索构造'}
|
||||
@@ -68,7 +68,7 @@ export function MetadataSyncCard({
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
disabled={status.active}
|
||||
placeholder="例如: hot subdwarf, Gaia BH1..."
|
||||
className="w-full px-4 py-2 rounded-lg bg-slate-50 border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:bg-white transition-all text-xs font-medium"
|
||||
className="w-full px-4 py-2 rounded-md bg-slate-50 border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-blueprint focus:bg-white transition-all text-xs font-medium"
|
||||
/>
|
||||
<div className="text-[11px] text-slate-450 flex flex-wrap gap-x-2.5 px-0.5 mt-1 select-none">
|
||||
<span>高级格式:</span>
|
||||
@@ -89,9 +89,9 @@ export function MetadataSyncCard({
|
||||
type="button"
|
||||
disabled={status.active}
|
||||
onClick={() => setSource(src.id as any)}
|
||||
className={`flex-1 py-2 rounded-lg text-xs font-bold border transition-all cursor-pointer ${
|
||||
className={`flex-1 py-2 rounded-md text-xs font-bold border transition-all cursor-pointer ${
|
||||
source === src.id
|
||||
? 'bg-sky-50 border-sky-300 text-sky-700 shadow-sm'
|
||||
? 'bg-slate-100 border-slate-350 text-slate-855 shadow-2xs'
|
||||
: 'btn-console btn-console-secondary'
|
||||
}`}
|
||||
>
|
||||
@@ -104,13 +104,13 @@ export function MetadataSyncCard({
|
||||
|
||||
{/* 动态表单生成器 */}
|
||||
{showBuilder && (
|
||||
<div className="p-4 rounded-lg bg-slate-50 border border-slate-200 space-y-3.5 transition-all">
|
||||
<div className="p-4 rounded-md bg-slate-50 border border-slate-200 space-y-3.5 transition-all">
|
||||
<div className="text-xs font-bold text-slate-700 flex justify-between items-center select-none">
|
||||
<span>高级条件生成器</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddRule}
|
||||
className="text-xs text-sky-600 hover:text-sky-750 font-bold cursor-pointer"
|
||||
className="text-xs text-slate-655 hover:text-slate-855 font-bold cursor-pointer"
|
||||
>
|
||||
+ 添加条件
|
||||
</button>
|
||||
@@ -158,14 +158,14 @@ export function MetadataSyncCard({
|
||||
? '例如: Althaus'
|
||||
: '请输入检索词...'
|
||||
}
|
||||
className="flex-1 px-3 py-1.5 rounded-lg bg-white border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 text-xs font-medium"
|
||||
className="flex-1 px-3 py-1.5 rounded-md bg-white border border-slate-300 text-slate-900 placeholder-slate-450 focus:outline-none focus:border-blueprint text-xs font-medium"
|
||||
/>
|
||||
|
||||
{rules.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveRule(idx)}
|
||||
className="text-red-600 hover:text-red-750 text-xs font-bold px-2 py-1.5 cursor-pointer"
|
||||
className="text-slate-500 hover:text-slate-800 text-xs font-bold px-2 py-1.5 cursor-pointer"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
@@ -187,7 +187,7 @@ export function MetadataSyncCard({
|
||||
value={limit}
|
||||
disabled={status.active}
|
||||
onChange={e => setLimit(Math.max(1, parseInt(e.target.value) || 0))}
|
||||
className="w-full px-4 py-2 rounded-lg bg-slate-50 border border-slate-300 text-slate-900 focus:outline-none focus:border-sky-500 transition-all text-xs font-medium"
|
||||
className="w-full px-4 py-2 rounded-md bg-slate-50 border border-slate-300 text-slate-900 focus:outline-none focus:border-blueprint transition-all text-xs font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -196,7 +196,7 @@ export function MetadataSyncCard({
|
||||
type="button"
|
||||
disabled={status.active || estimating}
|
||||
onClick={handleEstimate}
|
||||
className="flex-1 py-2 rounded-lg bg-white border border-slate-300 hover:bg-slate-50 text-slate-750 text-xs font-bold flex items-center justify-center gap-2 transition-all disabled:opacity-40 cursor-pointer"
|
||||
className="flex-1 py-2 rounded-md bg-white border border-slate-300 hover:bg-slate-50 text-slate-750 text-xs font-bold flex items-center justify-center gap-2 transition-all disabled:opacity-40 cursor-pointer"
|
||||
>
|
||||
{estimating ? <Loader className="w-3.5 h-3.5 animate-spin" /> : <RefreshCw className="w-3.5 h-3.5" />}
|
||||
估计目录总量
|
||||
@@ -205,7 +205,7 @@ export function MetadataSyncCard({
|
||||
type="button"
|
||||
disabled={status.active || !query.trim()}
|
||||
onClick={handleStartHarvest}
|
||||
className="btn-console btn-console-primary flex-1 py-2 rounded-lg text-xs font-bold flex items-center justify-center gap-2 disabled:opacity-40 cursor-pointer"
|
||||
className="btn-console btn-console-primary flex-1 py-2 rounded-md text-xs font-bold flex items-center justify-center gap-2 disabled:opacity-40 cursor-pointer"
|
||||
>
|
||||
<Play className="w-3.5 h-3.5" />
|
||||
启动元数据同步
|
||||
@@ -215,10 +215,10 @@ export function MetadataSyncCard({
|
||||
|
||||
{/* 预估结果 */}
|
||||
{estimatedCount !== null && !status.active && (
|
||||
<div className="p-4 rounded-lg bg-sky-50 border border-sky-200 flex gap-3 text-xs text-sky-850 items-center select-none animate-in fade-in duration-250">
|
||||
<Info className="w-4 h-4 shrink-0 text-sky-600" />
|
||||
<div className="p-4 rounded-md bg-slate-50 border border-slate-200 flex gap-3 text-xs text-slate-800 items-center select-none animate-in fade-in duration-250">
|
||||
<Info className="w-4 h-4 shrink-0 text-slate-500" />
|
||||
<div>
|
||||
检索库中估计有约 <strong className="text-sky-900 font-bold">{estimatedCount}</strong> 篇文献记录。
|
||||
检索库中估计有约 <strong className="text-slate-900 font-bold">{estimatedCount}</strong> 篇文献记录。
|
||||
{estimatedCount > limit ? ` 限制最大同步前 ${limit} 篇元数据。` : ' 将全部进行同步。'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -227,18 +227,18 @@ export function MetadataSyncCard({
|
||||
|
||||
{/* 实时同步进度 */}
|
||||
{(status.active || status.synced > 0) && (
|
||||
<div className="console-panel p-6 rounded-xl border border-slate-200 bg-white space-y-4 shadow-xs">
|
||||
<div className="console-panel p-6 rounded-lg border border-slate-200 bg-white space-y-4 shadow-sm">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h3 className="text-xs font-bold text-slate-850 flex items-center gap-2">
|
||||
{status.active ? (
|
||||
<>
|
||||
<Loader className="w-4 h-4 text-sky-600 animate-spin" />
|
||||
<Loader className="w-4 h-4 text-blueprint animate-spin" />
|
||||
<span>正在同步学术元数据...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 text-emerald-655" />
|
||||
<CheckCircle className="w-4 h-4 text-emerald-700" />
|
||||
<span>元数据目录同步完成</span>
|
||||
</>
|
||||
)}
|
||||
@@ -247,18 +247,18 @@ export function MetadataSyncCard({
|
||||
检索条件: <code className="bg-slate-100 px-1 py-0.5 rounded text-slate-700 font-mono">{status.query}</code> • 平台: {status.source === 'all' ? '全部' : status.source === 'ads' ? 'NASA ADS' : 'arXiv'}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs font-bold text-sky-700">{status.synced} / {status.total} 篇</span>
|
||||
<span className="text-xs font-bold text-slate-800">{status.synced} / {status.total} 篇</span>
|
||||
</div>
|
||||
|
||||
<div className="w-full h-3 rounded-full bg-slate-100 overflow-hidden border border-slate-200 select-none">
|
||||
<div className="w-full h-3 rounded-md bg-slate-100 overflow-hidden border border-slate-200 select-none">
|
||||
<div
|
||||
className="h-full bg-sky-600 transition-all duration-500 shadow-sm"
|
||||
className="h-full bg-blueprint transition-all duration-500 shadow-sm"
|
||||
style={{ width: `${percent}%` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{status.active && (status.source === 'all' || status.source === 'arxiv') ? (
|
||||
<div className="p-3 rounded-lg bg-amber-50 border border-amber-200 text-xs text-amber-700 flex items-start gap-1.5 select-none">
|
||||
<div className="p-3 rounded-md bg-slate-50 border border-slate-200 border-l-4 border-l-amber-500 text-xs text-slate-700 flex items-start gap-1.5 select-none">
|
||||
<Lightbulb className="w-4 h-4 shrink-0 mt-0.5" />
|
||||
<span>同步 arXiv 文献时系统会自动加设 3000 毫秒的安全限流延迟以规避服务器封锁。</span>
|
||||
</div>
|
||||
|
||||
+96
-7
@@ -2,20 +2,32 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "@tailwindcss/typography";
|
||||
|
||||
@theme {
|
||||
--color-main: var(--text-main);
|
||||
--color-muted: var(--text-muted);
|
||||
--color-card: var(--bg-card);
|
||||
--color-sidebar: var(--bg-sidebar);
|
||||
--color-blueprint: var(--accent-blueprint);
|
||||
--color-star: var(--accent-star);
|
||||
--color-precision: var(--border-precision);
|
||||
|
||||
--font-mono: var(--font-mono);
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
color-scheme: light;
|
||||
|
||||
/* 极简白蓝学术科技风配色 */
|
||||
--bg-main: #f4f6f9;
|
||||
/* 极极简白蓝学术科技风配色 - 升级为暖调纸张感学术护眼色 */
|
||||
--bg-main: #faf8f5; /* 学术纸张底色 */
|
||||
--bg-card: #ffffff;
|
||||
--bg-sidebar: #0f2540; /* 保持深色侧边栏作为界面骨架结构 */
|
||||
--text-main: #0a2540;
|
||||
--text-muted: #5c6b84;
|
||||
--text-main: #1e293b; /* 优雅深石板灰,对比度更佳 */
|
||||
--text-muted: #475569; /* 中度石板灰,提高可读性 */
|
||||
|
||||
--accent-blueprint: #106ba3;
|
||||
--accent-star: #d97706;
|
||||
--border-precision: #d2d8e2;
|
||||
--accent-blueprint: #0369a1; /* 低饱和度学术蓝 */
|
||||
--accent-star: #b45309; /* 经典低饱和度琥珀橙 */
|
||||
--border-precision: #e2e8f0; /* 极细轻量级分割线 */
|
||||
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
}
|
||||
@@ -143,3 +155,80 @@ mark {
|
||||
border-radius: 0.125rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* LaTeX-style academic publishing typography overrides */
|
||||
.prose {
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
/* Academic Three-Line Table (LaTeX booktabs style) */
|
||||
.prose table {
|
||||
width: 100% !important;
|
||||
border-collapse: collapse !important;
|
||||
border-top: 2px solid #334155 !important; /* thick top rule */
|
||||
border-bottom: 2px solid #334155 !important; /* thick bottom rule */
|
||||
margin-top: 1.5em !important;
|
||||
margin-bottom: 1.5em !important;
|
||||
font-size: 0.85em !important;
|
||||
}
|
||||
|
||||
.prose thead {
|
||||
border-bottom: 1.2px solid #475569 !important; /* mid rule */
|
||||
}
|
||||
|
||||
.prose thead th {
|
||||
font-weight: 700 !important;
|
||||
color: #1e293b !important;
|
||||
padding: 0.5em 0.75em !important;
|
||||
text-align: left !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.prose tbody tr {
|
||||
border-bottom: 0.5px solid #e2e8f0 !important; /* light horizontal line */
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.prose tbody tr:last-child {
|
||||
border-bottom: none !important; /* last row doesn't have thin border because of the thick table bottom rule */
|
||||
}
|
||||
|
||||
.prose tbody td {
|
||||
padding: 0.5em 0.75em !important;
|
||||
color: #334155 !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Remove vertical borders and striping that are generated by default */
|
||||
.prose table, .prose th, .prose td {
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
}
|
||||
|
||||
/* LaTeX Blockquote */
|
||||
.prose blockquote {
|
||||
font-style: italic !important;
|
||||
color: #475569 !important;
|
||||
border-left: 3px solid #64748b !important; /* solid charcoal left line */
|
||||
background-color: #fdfcf9 !important; /* warmer background for blockquotes */
|
||||
padding: 0.75em 1.25em !important;
|
||||
margin: 1.25em 0 !important;
|
||||
border-radius: 2px !important;
|
||||
font-weight: 500 !important;
|
||||
quotes: none !important;
|
||||
}
|
||||
|
||||
.prose blockquote p::before,
|
||||
.prose blockquote p::after {
|
||||
content: "" !important;
|
||||
}
|
||||
|
||||
/* Formulas and Math spacing */
|
||||
.prose .katex-display {
|
||||
margin: 1.2em 0 !important;
|
||||
padding: 0.5em !important;
|
||||
overflow-x: auto !important;
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ export function CitationPanel({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowSwitchMenu(!showSwitchMenu)}
|
||||
className="btn-console btn-console-secondary px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-2 transition-all cursor-pointer shrink-0"
|
||||
className="btn-console btn-console-secondary px-4 py-2 rounded-md text-xs font-bold flex items-center gap-2 transition-all cursor-pointer shrink-0"
|
||||
>
|
||||
<GitFork className="w-3.5 h-3.5 text-sky-600" />
|
||||
<GitFork className="w-3.5 h-3.5 text-blueprint" />
|
||||
<span>最近文献</span>
|
||||
<ChevronDown className={`w-3 h-3 transition-transform ${showSwitchMenu ? 'rotate-180' : ''}`} />
|
||||
</button>
|
||||
@@ -64,7 +64,7 @@ export function CitationPanel({
|
||||
{showSwitchMenu && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setShowSwitchMenu(false)} />
|
||||
<div className="absolute right-0 mt-1.5 w-72 rounded-xl bg-white border border-slate-200 shadow-xl py-1.5 z-55 text-xs max-h-96 overflow-y-auto scrollbar-thin">
|
||||
<div className="absolute right-0 mt-1.5 w-72 rounded-md bg-white border border-slate-200 shadow-md py-1.5 z-55 text-xs max-h-96 overflow-y-auto scrollbar-thin">
|
||||
{/* 最近文献 */}
|
||||
{recentlySelected.length > 0 && (
|
||||
<div className="border-b border-slate-100 pb-1.5 mb-1.5">
|
||||
@@ -81,7 +81,7 @@ export function CitationPanel({
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left hover:bg-slate-50 transition-colors flex flex-col gap-0.5 border-l-2 ${
|
||||
paper.bibcode === selectedPaper.bibcode
|
||||
? 'border-sky-500 bg-sky-50/30 text-sky-850 font-bold'
|
||||
? 'border-blueprint bg-blueprint/5 text-blueprint font-bold'
|
||||
: 'border-transparent text-slate-700 font-medium'
|
||||
}`}
|
||||
>
|
||||
@@ -112,7 +112,7 @@ export function CitationPanel({
|
||||
}}
|
||||
className={`w-full px-3 py-2 text-left hover:bg-slate-50 transition-colors flex flex-col gap-0.5 border-l-2 ${
|
||||
paper.bibcode === selectedPaper.bibcode
|
||||
? 'border-sky-500 bg-sky-50/30 text-sky-850 font-bold'
|
||||
? 'border-blueprint bg-blueprint/5 text-blueprint font-bold'
|
||||
: 'border-transparent text-slate-700 font-medium'
|
||||
}`}
|
||||
>
|
||||
@@ -127,7 +127,7 @@ export function CitationPanel({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2 border border-slate-200 bg-white px-3 py-1.5 rounded-lg shadow-sm">
|
||||
<div className="flex items-center gap-2 border border-slate-200 bg-white px-3 py-1.5 rounded-md shadow-sm">
|
||||
<span className="text-[11px] text-slate-500 font-bold">展示节点上限:</span>
|
||||
<input
|
||||
type="range"
|
||||
@@ -136,14 +136,14 @@ export function CitationPanel({
|
||||
step="5"
|
||||
value={nodeLimit}
|
||||
onChange={(e) => setNodeLimit(Number(e.target.value))}
|
||||
className="w-20 h-1.5 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-sky-600 focus:outline-none"
|
||||
className="w-20 h-1.5 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blueprint focus:outline-none"
|
||||
/>
|
||||
<span className="text-xs font-bold text-sky-600 w-6 text-right">{nodeLimit}</span>
|
||||
<span className="text-xs font-bold text-blueprint w-6 text-right">{nodeLimit}</span>
|
||||
</div>
|
||||
{selectedPaper && (
|
||||
<button
|
||||
onClick={() => loadCitations(selectedPaper.bibcode, true)}
|
||||
className="btn-console px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-2"
|
||||
className="btn-console px-4 py-2 rounded-md text-xs font-bold flex items-center gap-2"
|
||||
>
|
||||
<RotateCcw className="w-3.5 h-3.5" /> 重置引用星系图
|
||||
</button>
|
||||
@@ -152,12 +152,12 @@ export function CitationPanel({
|
||||
</div>
|
||||
|
||||
{loadingCitations ? (
|
||||
<div className="console-panel rounded-xl flex-1 flex flex-col items-center justify-center text-slate-500 bg-white">
|
||||
<Loader className="w-8 h-8 animate-spin text-sky-600 mb-2" />
|
||||
<div className="console-panel rounded-lg flex-1 flex flex-col items-center justify-center text-slate-500 bg-white">
|
||||
<Loader className="w-8 h-8 animate-spin text-blueprint mb-2" />
|
||||
<p className="text-xs font-bold text-slate-600">正在从馆藏数据库中提取引用拓扑数据...</p>
|
||||
</div>
|
||||
) : citationNetwork ? (
|
||||
<div className="flex-1 console-panel rounded-xl border border-slate-200 overflow-hidden relative flex flex-col md:flex-row bg-white min-h-0">
|
||||
<div className="flex-1 console-panel rounded-lg border border-slate-200 overflow-hidden relative flex flex-col md:flex-row bg-white min-h-0">
|
||||
{/* 可视化画布 */}
|
||||
<div className="flex-1 relative bg-slate-50/30 min-h-[350px] md:min-h-0">
|
||||
<CitationGalaxyCanvas
|
||||
@@ -170,7 +170,7 @@ export function CitationPanel({
|
||||
{/* 右侧关系详情面板 */}
|
||||
<div className="w-full md:w-80 h-64 md:h-auto border-t md:border-t-0 md:border-l border-slate-200 bg-slate-50 p-6 space-y-6 overflow-y-auto scrollbar-thin">
|
||||
<div>
|
||||
<span className="text-[10px] font-bold text-sky-700 tracking-wider block mb-2">● 中心焦点文献</span>
|
||||
<span className="text-[10px] font-bold text-blueprint tracking-wider block mb-2">● 中心焦点文献</span>
|
||||
<h4 className="text-xs font-bold text-slate-900 leading-relaxed font-sans">{citationNetwork.title}</h4>
|
||||
<p className="text-[10px] text-slate-600 font-mono mt-2 bg-slate-200/50 border border-slate-300/60 px-2 py-1.5 rounded select-all truncate">{citationNetwork.bibcode}</p>
|
||||
</div>
|
||||
@@ -187,7 +187,7 @@ export function CitationPanel({
|
||||
<div
|
||||
key={bib}
|
||||
onClick={() => handleNodeClick(bib)}
|
||||
className="text-[10px] text-slate-700 hover:text-sky-700 font-mono py-1.5 px-2.5 rounded bg-white border border-slate-200 hover:border-sky-300 cursor-pointer transition-all truncate"
|
||||
className="text-[10px] text-slate-700 hover:text-blueprint font-mono py-1.5 px-2.5 rounded bg-white border border-slate-200 hover:border-blueprint/40 cursor-pointer transition-all truncate"
|
||||
>
|
||||
{bib}
|
||||
</div>
|
||||
@@ -208,7 +208,7 @@ export function CitationPanel({
|
||||
<div
|
||||
key={bib}
|
||||
onClick={() => handleNodeClick(bib)}
|
||||
className="text-[10px] text-slate-700 hover:text-sky-700 font-mono py-1.5 px-2.5 rounded bg-white border border-slate-200 hover:border-sky-300 cursor-pointer transition-all truncate"
|
||||
className="text-[10px] text-slate-700 hover:text-blueprint font-mono py-1.5 px-2.5 rounded bg-white border border-slate-200 hover:border-blueprint/40 cursor-pointer transition-all truncate"
|
||||
>
|
||||
{bib}
|
||||
</div>
|
||||
@@ -219,7 +219,7 @@ export function CitationPanel({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="console-panel rounded-xl flex-1 flex flex-col items-center justify-center text-slate-500 bg-white">
|
||||
<div className="console-panel rounded-lg flex-1 flex flex-col items-center justify-center text-slate-500 bg-white">
|
||||
<GitFork className="w-12 h-12 mb-3 text-slate-300" />
|
||||
<p className="text-xs font-bold text-slate-500">无法拉取当前选定目标的星系引用关系</p>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Library, RotateCw, Search, SlidersHorizontal, X, CheckCircle, AlertTriangle, BookOpen, GitFork } from 'lucide-react';
|
||||
import type { StandardPaper } from '../types';
|
||||
import { getDoctypeBadge } from '../utils/paper';
|
||||
import { CustomSelect } from '../components/CustomSelect';
|
||||
import { PaperCard } from '../components/PaperCard';
|
||||
|
||||
interface LibraryPanelProps {
|
||||
library: StandardPaper[];
|
||||
@@ -188,7 +188,7 @@ export function LibraryPanel({
|
||||
<button
|
||||
onClick={handleResync}
|
||||
disabled={syncing}
|
||||
className="btn-console px-4 py-2 rounded-lg text-xs font-bold flex items-center gap-2 disabled:opacity-50 transition-all"
|
||||
className="btn-console px-4 py-2 rounded-md text-xs font-bold flex items-center gap-2 disabled:opacity-50 transition-all"
|
||||
>
|
||||
<RotateCw className={`w-3.5 h-3.5 ${syncing ? 'animate-spin' : ''}`} />
|
||||
{syncing ? '正在同步...' : '重新同步馆藏'}
|
||||
@@ -197,10 +197,10 @@ export function LibraryPanel({
|
||||
|
||||
{/* 同步结果反馈条 */}
|
||||
{syncFeedback && (
|
||||
<div className={`px-4 py-2.5 rounded-lg text-xs font-bold flex items-center gap-2 transition-all ${
|
||||
<div className={`px-4 py-2.5 rounded-md text-xs font-bold flex items-center gap-2 transition-all ${
|
||||
syncFeedback.type === 'success'
|
||||
? 'bg-emerald-50 border border-emerald-200 text-emerald-700'
|
||||
: 'bg-red-50 border border-red-200 text-red-700'
|
||||
? 'bg-emerald-50 border border-emerald-200/60 text-emerald-850'
|
||||
: 'bg-rose-50 border border-rose-200/60 text-rose-850'
|
||||
}`}>
|
||||
{syncFeedback.type === 'success'
|
||||
? <CheckCircle className="w-3.5 h-3.5 shrink-0" />
|
||||
@@ -211,7 +211,7 @@ export function LibraryPanel({
|
||||
|
||||
{/* 搜索、筛选与排序工具栏 */}
|
||||
{library.length > 0 && (
|
||||
<div className="flex flex-col gap-3.5 bg-white p-4 rounded-xl border border-slate-200 shadow-sm text-xs font-semibold text-slate-700">
|
||||
<div className="flex flex-col gap-3.5 bg-white p-4 rounded-lg border border-slate-200 shadow-sm text-xs font-semibold text-slate-700">
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
{/* 本地检索 */}
|
||||
<div className="flex-1 space-y-1.5">
|
||||
@@ -223,7 +223,7 @@ export function LibraryPanel({
|
||||
value={searchTerm}
|
||||
onChange={e => setSearchTerm(e.target.value)}
|
||||
placeholder="搜索文献标题、作者、摘要、Bibcode、arXiv ID 或 DOI..."
|
||||
className="w-full pl-9 pr-8 py-2 rounded-lg bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:bg-white transition-all text-xs font-medium"
|
||||
className="w-full pl-9 pr-8 py-2 rounded-md bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-blueprint focus:bg-white transition-all text-xs font-medium"
|
||||
/>
|
||||
{searchTerm && (
|
||||
<button
|
||||
@@ -305,9 +305,9 @@ export function LibraryPanel({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||
className={`w-full sm:w-auto px-4 py-2 rounded-lg border text-xs font-bold transition-all shrink-0 flex items-center justify-center gap-1.5 cursor-pointer ${
|
||||
className={`w-full sm:w-auto px-4 py-2 rounded-md border text-xs font-bold transition-all shrink-0 flex items-center justify-center gap-1.5 cursor-pointer ${
|
||||
showAdvanced
|
||||
? 'bg-sky-50 border-sky-300 text-sky-700'
|
||||
? 'bg-slate-100 border-slate-350 text-slate-855'
|
||||
: 'btn-console btn-console-secondary'
|
||||
}`}
|
||||
>
|
||||
@@ -328,7 +328,7 @@ export function LibraryPanel({
|
||||
value={filterAuthor}
|
||||
onChange={e => setFilterAuthor(e.target.value)}
|
||||
placeholder="如: Althaus..."
|
||||
className="w-full px-3 py-2 rounded-lg bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:bg-white transition-all text-xs font-medium"
|
||||
className="w-full px-3 py-2 rounded-md bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-blueprint focus:bg-white transition-all text-xs font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -340,7 +340,7 @@ export function LibraryPanel({
|
||||
value={filterYear}
|
||||
onChange={e => setFilterYear(e.target.value)}
|
||||
placeholder="如: 2023..."
|
||||
className="w-full px-3 py-2 rounded-lg bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:bg-white transition-all text-xs font-medium"
|
||||
className="w-full px-3 py-2 rounded-md bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-blueprint focus:bg-white transition-all text-xs font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -352,7 +352,7 @@ export function LibraryPanel({
|
||||
value={filterJournal}
|
||||
onChange={e => setFilterJournal(e.target.value)}
|
||||
placeholder="如: ApJ 或 MNRAS..."
|
||||
className="w-full px-3 py-2 rounded-lg bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:bg-white transition-all text-xs font-medium"
|
||||
className="w-full px-3 py-2 rounded-md bg-slate-50 border border-slate-250 text-slate-900 placeholder-slate-450 focus:outline-none focus:border-blueprint focus:bg-white transition-all text-xs font-medium"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -361,19 +361,19 @@ export function LibraryPanel({
|
||||
)}
|
||||
|
||||
{library.length === 0 ? (
|
||||
<div className="console-panel p-16 rounded-xl text-center border-2 border-dashed border-slate-300">
|
||||
<div className="console-panel p-16 rounded-lg text-center border-2 border-dashed border-slate-300">
|
||||
<Library className="w-12 h-12 text-slate-400 mx-auto mb-4" />
|
||||
<h3 className="font-bold text-slate-800 text-sm mb-2">本地文献馆藏为空</h3>
|
||||
<p className="text-xs text-slate-500 mb-6 max-w-md mx-auto">您在检索控制台下载的天体文献会自动同步离线保存到这里,方便进行无网双语解析阅读。</p>
|
||||
<button
|
||||
onClick={() => setActiveTab('search')}
|
||||
className="btn-console btn-console-primary px-6 py-2.5 rounded-lg text-xs font-bold"
|
||||
className="btn-console btn-console-primary px-6 py-2.5 rounded-md text-xs font-bold"
|
||||
>
|
||||
前往检索文献资源
|
||||
</button>
|
||||
</div>
|
||||
) : sortedLibrary.length === 0 ? (
|
||||
<div className="console-panel p-12 rounded-xl text-center border border-slate-200 bg-white">
|
||||
<div className="console-panel p-12 rounded-lg text-center border border-slate-200 bg-white">
|
||||
<SlidersHorizontal className="w-10 h-10 text-slate-350 mx-auto mb-3" />
|
||||
<h3 className="font-bold text-slate-700 text-xs mb-1.5">未找到符合筛选条件的文献</h3>
|
||||
<p className="text-xs text-slate-450 max-w-sm mx-auto mb-4">请尝试修改您的馆藏检索关键词或放宽离线状态及高级元数据筛选条件。</p>
|
||||
@@ -386,7 +386,7 @@ export function LibraryPanel({
|
||||
setFilterYear('');
|
||||
setFilterJournal('');
|
||||
}}
|
||||
className="px-4 py-1.5 bg-slate-100 hover:bg-slate-200 text-slate-650 rounded-lg text-xs font-bold transition-all cursor-pointer"
|
||||
className="px-4 py-1.5 bg-slate-100 hover:bg-slate-200 text-slate-655 rounded-md text-xs font-bold transition-all cursor-pointer"
|
||||
>
|
||||
重置全部检索与过滤条件
|
||||
</button>
|
||||
@@ -399,66 +399,53 @@ export function LibraryPanel({
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{sortedLibrary.map(paper => {
|
||||
return (
|
||||
<div
|
||||
key={paper.bibcode}
|
||||
<PaperCard
|
||||
key={paper.bibcode}
|
||||
paper={paper}
|
||||
onClick={() => onShowDetail(paper)}
|
||||
className="console-panel p-5 rounded-xl border border-slate-200 hover:border-sky-350 bg-white flex flex-col justify-between relative overflow-hidden group transition-all cursor-pointer shadow-sm"
|
||||
>
|
||||
{/* 状态角标 */}
|
||||
<div
|
||||
title={
|
||||
(!paper.is_downloaded && (paper.pdf_error || paper.html_error))
|
||||
? (paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource')
|
||||
? '已手动标记为【无有效全文资源】,批量下载时自动跳过。'
|
||||
: `下载失败原因:${[paper.pdf_error, paper.html_error].filter(Boolean).join('; ')}`
|
||||
: undefined
|
||||
variant="grid"
|
||||
statusBadge={
|
||||
<div
|
||||
title={
|
||||
(!paper.is_downloaded && (paper.pdf_error || paper.html_error))
|
||||
? (paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource')
|
||||
? '已手动标记为【无有效全文资源】,批量下载时自动跳过。'
|
||||
: `下载失败原因:${[paper.pdf_error, paper.html_error].filter(Boolean).join('; ')}`
|
||||
: undefined
|
||||
}
|
||||
className={`absolute top-0 right-0 px-2 py-0.5 text-[9px] font-bold border-b border-l rounded-bl ${
|
||||
paper.has_vector
|
||||
? 'bg-amber-50 text-amber-700 border-amber-200/60'
|
||||
: paper.has_translation
|
||||
? 'bg-emerald-50 text-emerald-850 border-emerald-200/60'
|
||||
: paper.has_markdown
|
||||
? 'bg-blue-50 text-blue-800 border-blue-200/60'
|
||||
: paper.is_downloaded
|
||||
? 'bg-slate-100 text-slate-800 border-slate-250'
|
||||
: (paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource')
|
||||
? 'bg-slate-50 text-slate-550 border-slate-200 cursor-help'
|
||||
: (paper.pdf_error || paper.html_error)
|
||||
? 'bg-rose-50 text-rose-850 border-rose-200/60 cursor-help'
|
||||
: 'bg-slate-50 text-slate-550 border-slate-100'
|
||||
}`}
|
||||
>
|
||||
{paper.has_vector
|
||||
? '已向量化'
|
||||
: paper.has_translation
|
||||
? '已翻译'
|
||||
: paper.has_markdown
|
||||
? '已解析'
|
||||
: paper.is_downloaded
|
||||
? '已下载'
|
||||
: (paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource')
|
||||
? '无资源'
|
||||
: (paper.pdf_error || paper.html_error)
|
||||
? '下载失败'
|
||||
: '未下载'}
|
||||
</div>
|
||||
}
|
||||
className={`absolute top-0 right-0 px-2 py-0.5 text-[9px] font-bold border-b border-l rounded-bl ${
|
||||
paper.has_vector
|
||||
? 'bg-amber-50 text-amber-700 border-amber-200'
|
||||
: paper.has_translation
|
||||
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
|
||||
: paper.has_markdown
|
||||
? 'bg-sky-50 text-sky-700 border-sky-200'
|
||||
: paper.is_downloaded
|
||||
? 'bg-indigo-50 text-indigo-700 border-indigo-200'
|
||||
: (paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource')
|
||||
? 'bg-slate-100 text-slate-600 border-slate-200 cursor-help'
|
||||
: (paper.pdf_error || paper.html_error)
|
||||
? 'bg-rose-50 text-rose-700 border-rose-200 cursor-help'
|
||||
: 'bg-slate-50 text-slate-500 border-slate-100'
|
||||
}`}
|
||||
>
|
||||
{paper.has_vector
|
||||
? '已向量化'
|
||||
: paper.has_translation
|
||||
? '已翻译'
|
||||
: paper.has_markdown
|
||||
? '已解析'
|
||||
: paper.is_downloaded
|
||||
? '已下载'
|
||||
: (paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource')
|
||||
? '无资源'
|
||||
: (paper.pdf_error || paper.html_error)
|
||||
? '下载失败'
|
||||
: '未下载'}
|
||||
</div>
|
||||
|
||||
<div className="pr-10">
|
||||
<div>
|
||||
<h3 className="font-bold text-xs text-slate-900 line-clamp-2 hover:text-sky-700 transition-all leading-relaxed">
|
||||
{getDoctypeBadge(paper.doctype)}
|
||||
<span className="align-middle">{paper.title}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="text-[10px] text-slate-500 font-semibold mt-1.5 uppercase">
|
||||
作者: {paper.authors.slice(0, 2).join(', ')}{paper.authors.length > 2 ? ' 等' : ''} | 发表年份: {paper.year}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between border-t border-slate-100 pt-3 mt-4 text-[10px]">
|
||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||
{paper.is_downloaded && (
|
||||
footerLeft={
|
||||
paper.is_downloaded && (
|
||||
<div className="flex gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
@@ -466,7 +453,7 @@ export function LibraryPanel({
|
||||
e.stopPropagation();
|
||||
onOpenReader(paper);
|
||||
}}
|
||||
className="px-2 py-0.5 rounded bg-sky-50 hover:bg-sky-100 text-sky-700 border border-sky-200 font-bold transition-all text-[9px] flex items-center gap-1 shadow-2xs cursor-pointer animate-fade-in"
|
||||
className="px-2.5 py-1 rounded bg-slate-100 hover:bg-slate-200 text-slate-800 border border-slate-250 font-bold transition-all text-[9px] flex items-center gap-1 shadow-2xs cursor-pointer animate-fade-in"
|
||||
>
|
||||
<BookOpen className="w-3 h-3" />
|
||||
<span>阅读</span>
|
||||
@@ -477,38 +464,33 @@ export function LibraryPanel({
|
||||
e.stopPropagation();
|
||||
onOpenCitation(paper);
|
||||
}}
|
||||
className="px-2 py-0.5 rounded bg-slate-50 hover:bg-slate-100 text-slate-700 border border-slate-200 font-bold transition-all text-[9px] flex items-center gap-1 shadow-2xs cursor-pointer animate-fade-in"
|
||||
className="px-2.5 py-1 rounded bg-slate-50 hover:bg-slate-150 text-slate-655 border border-slate-250 font-bold transition-all text-[9px] flex items-center gap-1 shadow-2xs cursor-pointer animate-fade-in"
|
||||
>
|
||||
<GitFork className="w-3 h-3" />
|
||||
<span>图谱</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0 self-end">
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${paper.has_markdown ? 'bg-sky-500' : 'bg-slate-200'}`}
|
||||
/>
|
||||
<span className="text-slate-400 text-[9px]">{paper.has_markdown ? '正文' : '未解析'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${paper.has_translation ? 'bg-emerald-500' : 'bg-slate-200'}`}
|
||||
/>
|
||||
<span className="text-slate-400 text-[9px]">{paper.has_translation ? '翻译' : '未翻译'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${paper.has_vector ? 'bg-amber-500' : 'bg-slate-200'}`}
|
||||
/>
|
||||
<span className="text-slate-400 text-[9px]">{paper.has_vector ? '向量库' : '未向量化'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
)
|
||||
}
|
||||
footerRight={
|
||||
<>
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${paper.has_translation ? 'bg-emerald-500' : 'bg-slate-200'}`}
|
||||
/>
|
||||
<span className="text-slate-400 text-[9px]">{paper.has_translation ? '翻译' : '未翻译'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span
|
||||
className={`w-1.5 h-1.5 rounded-full ${paper.has_vector ? 'bg-amber-500' : 'bg-slate-200'}`}
|
||||
/>
|
||||
<span className="text-slate-400 text-[9px]">{paper.has_vector ? '向量库' : '未向量化'}</span>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function ResearchAgentPanel({ showConfirm, showAlert }: ResearchAgentPane
|
||||
const state = useResearchAgent({ showConfirm, showAlert });
|
||||
|
||||
return (
|
||||
<div className="w-full flex-1 flex overflow-hidden bg-slate-100 rounded-2xl border border-slate-200 h-[calc(100vh-130px)] shadow-xs">
|
||||
<div className="w-full flex-1 flex overflow-hidden bg-slate-100 rounded-lg border border-slate-200 h-[calc(100vh-130px)] shadow-sm">
|
||||
{/* 左侧会话列表侧栏 */}
|
||||
<AgentSessionSidebar
|
||||
sessions={state.sessions}
|
||||
|
||||
@@ -3,8 +3,7 @@ import React from 'react';
|
||||
import { Search, Loader, CheckCircle, Copy, Download, ChevronLeft, ChevronRight, SlidersHorizontal, AlertTriangle, Lightbulb } from 'lucide-react';
|
||||
import type { StandardPaper } from '../types';
|
||||
import { CustomSelect } from '../components/CustomSelect';
|
||||
|
||||
import { getDoctypeBadge } from '../utils/paper';
|
||||
import { PaperCard } from '../components/PaperCard';
|
||||
|
||||
interface SearchPanelProps {
|
||||
searchQuery: string;
|
||||
@@ -125,7 +124,7 @@ export function SearchPanel({
|
||||
</div>
|
||||
|
||||
{/* 检索控制面板 */}
|
||||
<div className="console-panel p-6 rounded-xl space-y-4">
|
||||
<div className="console-panel p-6 rounded-lg space-y-4">
|
||||
<form onSubmit={handleSearch} className="space-y-4">
|
||||
<div className="flex flex-col md:flex-row gap-3 items-stretch">
|
||||
<div className="relative flex-1">
|
||||
@@ -135,16 +134,16 @@ export function SearchPanel({
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
placeholder="检索天文学文献 (支持关键字、作者、发表年份,如 'hot subdwarf year:2020-2023')"
|
||||
className="w-full pl-12 pr-4 py-3 rounded-lg bg-slate-50 border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:bg-white transition-all text-sm font-medium"
|
||||
className="w-full pl-12 pr-4 py-3 rounded-md bg-slate-50 border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-slate-500 focus:bg-white transition-all text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowBuilder(!showBuilder)}
|
||||
className={`px-4 py-3 rounded-lg border text-xs font-bold transition-all shrink-0 flex items-center gap-1.5 ${
|
||||
className={`px-4 py-3 rounded-md border text-xs font-bold transition-all shrink-0 flex items-center gap-1.5 ${
|
||||
showBuilder
|
||||
? 'bg-sky-50 border-sky-300 text-sky-700'
|
||||
? 'bg-slate-100 border-slate-350 text-slate-855'
|
||||
: 'btn-console'
|
||||
}`}
|
||||
>
|
||||
@@ -154,7 +153,7 @@ export function SearchPanel({
|
||||
<button
|
||||
type="submit"
|
||||
disabled={searching}
|
||||
className="btn-console btn-console-primary px-6 py-3 rounded-lg text-xs font-bold tracking-wider flex items-center gap-2 shrink-0"
|
||||
className="btn-console btn-console-primary px-6 py-3 rounded-md text-xs font-bold tracking-wider flex items-center gap-2 shrink-0"
|
||||
>
|
||||
{searching ? <Loader className="w-3.5 h-3.5 animate-spin" /> : <Search className="w-3.5 h-3.5" />}
|
||||
{searching ? '检索中...' : '检索文献'}
|
||||
@@ -164,13 +163,13 @@ export function SearchPanel({
|
||||
|
||||
{/* 条件构造器 */}
|
||||
{showBuilder && (
|
||||
<div className="p-4 rounded-lg bg-slate-50 border border-slate-200 space-y-3 transition-all">
|
||||
<div className="p-4 rounded-md bg-slate-50 border border-slate-200 space-y-3 transition-all">
|
||||
<div className="text-xs font-bold text-slate-700 flex justify-between items-center">
|
||||
<span>高级检索式条件构造器</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddRule}
|
||||
className="text-xs text-sky-600 hover:text-sky-700 font-bold"
|
||||
className="text-xs text-slate-700 hover:text-slate-900 font-bold"
|
||||
>
|
||||
+ 添加检索条件
|
||||
</button>
|
||||
@@ -218,14 +217,14 @@ export function SearchPanel({
|
||||
? '例如: Althaus'
|
||||
: '请输入检索词...'
|
||||
}
|
||||
className="flex-1 px-3 py-1.5 rounded-lg bg-white border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-sky-500 text-xs font-medium"
|
||||
className="flex-1 px-3 py-1.5 rounded-md bg-white border border-slate-300 text-slate-900 placeholder-slate-400 focus:outline-none focus:border-slate-500 text-xs font-medium"
|
||||
/>
|
||||
|
||||
{rules.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveRule(idx)}
|
||||
className="text-red-655 hover:text-red-700 text-xs font-bold px-2 py-1.5"
|
||||
className="text-slate-600 hover:text-slate-800 text-xs font-bold px-2 py-1.5"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
@@ -260,9 +259,9 @@ export function SearchPanel({
|
||||
name="searchSource"
|
||||
checked={searchSource === src.id}
|
||||
onChange={() => setSearchSource(src.id as any)}
|
||||
className="accent-sky-600"
|
||||
className="accent-slate-700"
|
||||
/>
|
||||
<span className={searchSource === src.id ? 'text-sky-750 font-bold' : 'text-slate-500 hover:text-slate-700'}>
|
||||
<span className={searchSource === src.id ? 'text-slate-900 font-bold' : 'text-slate-500 hover:text-slate-700'}>
|
||||
{src.label}
|
||||
</span>
|
||||
</label>
|
||||
@@ -304,7 +303,7 @@ export function SearchPanel({
|
||||
type="button"
|
||||
onClick={handleExportBibtex}
|
||||
disabled={exporting}
|
||||
className="btn-console btn-console-secondary px-3 py-1.5 rounded-lg text-xs font-bold flex items-center gap-1.5"
|
||||
className="btn-console btn-console-secondary px-3 py-1.5 rounded-md text-xs font-bold flex items-center gap-1.5"
|
||||
>
|
||||
{exporting ? <Loader className="w-3.5 h-3.5 animate-spin" /> : null}
|
||||
导出已选 ({exportingList.length}) 篇 BibTeX
|
||||
@@ -317,11 +316,11 @@ export function SearchPanel({
|
||||
|
||||
{/* BibTeX 导出显示 */}
|
||||
{bibtexContent && (
|
||||
<div className="console-panel p-5 rounded-xl border border-sky-200 relative overflow-hidden bg-sky-50/20">
|
||||
<h3 className="text-xs font-bold text-sky-700 mb-3 flex items-center gap-2">
|
||||
<div className="console-panel p-5 rounded-lg border border-slate-200 relative overflow-hidden bg-slate-50/20">
|
||||
<h3 className="text-xs font-bold text-slate-700 mb-3 flex items-center gap-2">
|
||||
<CheckCircle className="w-4 h-4 text-emerald-600" /> BibTeX 导出成功
|
||||
</h3>
|
||||
<pre className="bg-white p-4 rounded-lg border border-slate-200 text-xs text-slate-800 font-mono overflow-x-auto whitespace-pre-wrap max-h-60 leading-relaxed shadow-inner">
|
||||
<pre className="bg-white p-4 rounded-md border border-slate-200 text-xs text-slate-800 font-mono overflow-x-auto whitespace-pre-wrap max-h-60 leading-relaxed shadow-inner">
|
||||
{bibtexContent}
|
||||
</pre>
|
||||
<button
|
||||
@@ -329,7 +328,7 @@ export function SearchPanel({
|
||||
navigator.clipboard.writeText(bibtexContent);
|
||||
showAlert('BibTeX 数据已成功复制至剪贴板。', '复制成功');
|
||||
}}
|
||||
className="absolute top-5 right-5 btn-console btn-console-secondary px-3 py-1 rounded-lg text-xs font-bold flex items-center gap-1"
|
||||
className="absolute top-5 right-5 btn-console btn-console-secondary px-3 py-1 rounded-md text-xs font-bold flex items-center gap-1"
|
||||
>
|
||||
<Copy className="w-3.5 h-3.5" /> 复制数据
|
||||
</button>
|
||||
@@ -339,9 +338,9 @@ export function SearchPanel({
|
||||
{/* 检索列表 */}
|
||||
<div className="relative min-h-[200px]">
|
||||
{searching && (
|
||||
<div className="absolute inset-0 bg-white/60 backdrop-blur-[1px] z-10 flex items-center justify-center rounded-xl">
|
||||
<div className="flex flex-col items-center gap-3 bg-white p-6 rounded-xl shadow-lg border border-slate-200 text-center">
|
||||
<Loader className="w-8 h-8 text-sky-650 animate-spin" />
|
||||
<div className="absolute inset-0 bg-white/60 backdrop-blur-[1px] z-10 flex items-center justify-center rounded-lg">
|
||||
<div className="flex flex-col items-center gap-3 bg-white p-6 rounded-lg shadow-sm border border-slate-200 text-center">
|
||||
<Loader className="w-8 h-8 text-slate-600 animate-spin" />
|
||||
<span className="text-xs font-bold text-slate-600">正在与学术服务器通讯,检索文献数据中...</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -351,64 +350,48 @@ export function SearchPanel({
|
||||
const isDownloading = downloadingBibcodes[paper.bibcode] || false;
|
||||
const isSelected = selectedPaper?.bibcode === paper.bibcode;
|
||||
return (
|
||||
<div
|
||||
key={paper.bibcode}
|
||||
className={`console-panel p-6 rounded-xl border transition-all relative ${
|
||||
isSelected ? 'border-sky-500 bg-sky-50/10' : 'border-slate-200 hover:border-slate-350 hover:bg-white'
|
||||
}`}
|
||||
>
|
||||
{/* 选定指示侧条 */}
|
||||
{isSelected && <div className="absolute top-0 left-0 w-1.5 h-full bg-sky-600" />}
|
||||
|
||||
<div className="flex flex-col md:flex-row md:justify-between md:items-start gap-4 mb-3">
|
||||
<div className="flex-1">
|
||||
<h3
|
||||
className="font-bold text-sm text-slate-900 hover:text-sky-700 cursor-pointer transition-all line-clamp-2 leading-relaxed"
|
||||
onClick={() => openReader(paper)}
|
||||
>
|
||||
{getDoctypeBadge(paper.doctype)}
|
||||
<span className="align-middle">{paper.title}</span>
|
||||
</h3>
|
||||
<p className="text-xs text-slate-500 font-medium mt-2 leading-snug">
|
||||
作者: <span className="text-slate-800">{paper.authors.slice(0, 5).join(', ')}{paper.authors.length > 5 ? ' 等' : ''}</span> • 年份: <span className="text-slate-850 font-bold">{paper.year}</span> • 出版期刊: <span className="italic text-slate-700">{paper.pub_journal || '未标注'}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 shrink-0 items-center">
|
||||
<PaperCard
|
||||
key={paper.bibcode}
|
||||
paper={paper}
|
||||
isSelected={isSelected}
|
||||
onClick={() => openReader(paper)}
|
||||
variant="list"
|
||||
headerActions={
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onShowDetail(paper)}
|
||||
className="px-3 py-1.5 rounded-lg bg-white border border-slate-250 text-slate-650 hover:bg-slate-50 hover:border-slate-350 transition-all text-xs font-bold flex items-center cursor-pointer shadow-sm"
|
||||
className="px-3 py-1.5 rounded-md bg-white border border-slate-250 text-slate-655 hover:bg-slate-50 hover:border-slate-350 transition-all text-xs font-bold flex items-center cursor-pointer"
|
||||
>
|
||||
详情
|
||||
</button>
|
||||
{paper.is_downloaded ? (
|
||||
<span className="px-2.5 py-1 rounded-lg bg-emerald-50 text-emerald-700 border border-emerald-200 text-xs font-bold flex items-center gap-1">
|
||||
<span className="px-2.5 py-1 rounded-md bg-emerald-50 text-emerald-800 border border-emerald-200/60 text-xs font-bold flex items-center gap-1">
|
||||
<CheckCircle className="w-3.5 h-3.5" /> 已下载
|
||||
</span>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
{(paper.pdf_error || paper.html_error) && (
|
||||
paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource' ? (
|
||||
<span
|
||||
title="已手动标记为【无有效全文资源】,批量下载时自动跳过"
|
||||
className="px-2 py-1 bg-slate-50 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-lg cursor-help flex items-center gap-1"
|
||||
>
|
||||
<AlertTriangle className="w-3 h-3 text-slate-400" /> 无资源
|
||||
</span>
|
||||
) : (
|
||||
{paper.pdf_error === 'no_resource' && paper.html_error === 'no_resource' ? (
|
||||
<span
|
||||
title="已手动标记为【无有效全文资源】,批量下载时自动跳过"
|
||||
className="px-2 py-1 bg-slate-50 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-md cursor-help flex items-center gap-1"
|
||||
>
|
||||
<AlertTriangle className="w-3 h-3 text-slate-455" /> 无资源
|
||||
</span>
|
||||
) : (
|
||||
(paper.pdf_error || paper.html_error) && (
|
||||
<span
|
||||
title={`自动下载失败:${[paper.pdf_error, paper.html_error].filter(Boolean).join('; ')}`}
|
||||
className="px-2 py-1 bg-rose-50 text-rose-700 border border-rose-200 text-[10px] font-bold rounded-lg cursor-help flex items-center gap-1"
|
||||
className="px-2 py-1 bg-rose-50 text-rose-800 border border-rose-200/60 text-[10px] font-bold rounded-md cursor-help flex items-center gap-1"
|
||||
>
|
||||
<AlertTriangle className="w-3 h-3 text-rose-500" /> 下载失败
|
||||
<AlertTriangle className="w-3 h-3 text-rose-600" /> 下载失败
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleDownload(paper.bibcode)}
|
||||
disabled={isDownloading}
|
||||
className="btn-console btn-console-primary px-3 py-1.5 rounded-lg text-xs font-bold flex items-center gap-1 transition-all"
|
||||
className="btn-console btn-console-primary px-3 py-1.5 rounded-md text-xs font-bold flex items-center gap-1 transition-all"
|
||||
>
|
||||
{isDownloading ? <Loader className="w-3.5 h-3.5 animate-spin" /> : <Download className="w-3.5 h-3.5" />}
|
||||
下载文献
|
||||
@@ -416,27 +399,27 @@ export function SearchPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="flex items-center gap-1.5 cursor-pointer border border-slate-250 px-2.5 py-1.5 rounded-lg bg-slate-50 hover:bg-slate-100 text-slate-700 transition-all text-xs font-semibold">
|
||||
<label className="flex items-center gap-1.5 cursor-pointer border border-slate-250 px-2.5 py-1.5 rounded-md bg-slate-50 hover:bg-slate-100 text-slate-700 transition-all text-xs font-semibold">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={exportingList.includes(paper.bibcode)}
|
||||
onChange={() => toggleExportItem(paper.bibcode)}
|
||||
className="rounded text-sky-600 border-slate-300"
|
||||
className="rounded text-slate-700 border-slate-300"
|
||||
/>
|
||||
<span>选择</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-slate-600 line-clamp-3 leading-relaxed mb-4 font-normal">
|
||||
{paper.abstract_text || '暂无文献摘要数据。'}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-between items-stretch sm:items-center border-t border-slate-100 pt-4 gap-3">
|
||||
<div className="flex gap-2">
|
||||
</>
|
||||
}
|
||||
abstract={
|
||||
<p className="text-xs text-slate-600 line-clamp-3 leading-relaxed mb-4 font-normal">
|
||||
{paper.abstract_text || '暂无文献摘要数据。'}
|
||||
</p>
|
||||
}
|
||||
footerLeft={
|
||||
<>
|
||||
<button
|
||||
onClick={() => openReader(paper)}
|
||||
className="px-4 py-1.5 rounded-lg bg-sky-50 border border-sky-200 text-xs font-bold text-sky-700 hover:bg-sky-100 transition-all"
|
||||
className="px-4 py-1.5 rounded-md bg-slate-100 border border-slate-250 text-xs font-bold text-slate-800 hover:bg-slate-200 transition-all"
|
||||
>
|
||||
双语阅读对照
|
||||
</button>
|
||||
@@ -446,19 +429,20 @@ export function SearchPanel({
|
||||
setActiveTab('citation');
|
||||
loadCitations(paper.bibcode, true);
|
||||
}}
|
||||
className="px-4 py-1.5 rounded-lg bg-slate-50 border border-slate-250 text-xs font-bold text-slate-600 hover:border-slate-400 hover:text-slate-800 transition-all"
|
||||
className="px-4 py-1.5 rounded-md bg-slate-50 border border-slate-250 text-xs font-bold text-slate-600 hover:border-slate-400 hover:text-slate-800 transition-all"
|
||||
>
|
||||
引用星系图
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-slate-450 flex flex-wrap gap-x-4 gap-y-1 font-mono">
|
||||
</>
|
||||
}
|
||||
footerRight={
|
||||
<>
|
||||
{paper.doi && <span>DOI: {paper.doi}</span>}
|
||||
<span>Bibcode: <span className="font-semibold text-slate-700">{paper.bibcode}</span></span>
|
||||
{paper.citation_count > 0 && <span>被引次数: <span className="text-sky-750 font-bold">{paper.citation_count}</span></span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
@@ -466,11 +450,11 @@ export function SearchPanel({
|
||||
|
||||
{/* 分页控制栏 */}
|
||||
{searchResults.length > 0 && (
|
||||
<div className="flex items-center justify-between p-4 bg-white border border-slate-200 rounded-xl max-w-5xl mx-auto shadow-sm">
|
||||
<div className="flex items-center justify-between p-4 bg-white border border-slate-200 rounded-lg max-w-5xl mx-auto shadow-sm">
|
||||
<button
|
||||
onClick={() => handlePageChange(searchStart - searchRows)}
|
||||
disabled={!hasPreviousPage || searching}
|
||||
className="px-4 py-2 border border-slate-250 rounded-lg bg-white text-xs font-bold text-slate-700 hover:bg-slate-50 disabled:opacity-40 disabled:hover:bg-white flex items-center gap-1 transition-all"
|
||||
className="px-4 py-2 border border-slate-250 rounded-md bg-white text-xs font-bold text-slate-700 hover:bg-slate-50 disabled:opacity-40 disabled:hover:bg-white flex items-center gap-1 transition-all"
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" /> 上一页
|
||||
</button>
|
||||
@@ -482,7 +466,7 @@ export function SearchPanel({
|
||||
<button
|
||||
onClick={() => handlePageChange(searchStart + searchRows)}
|
||||
disabled={!hasNextPage || searching}
|
||||
className="px-4 py-2 border border-slate-250 rounded-lg bg-white text-xs font-bold text-slate-700 hover:bg-slate-50 disabled:opacity-40 disabled:hover:bg-white flex items-center gap-1 transition-all"
|
||||
className="px-4 py-2 border border-slate-250 rounded-md bg-white text-xs font-bold text-slate-700 hover:bg-slate-50 disabled:opacity-40 disabled:hover:bg-white flex items-center gap-1 transition-all"
|
||||
>
|
||||
下一页 <ChevronRight className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
@@ -8,16 +8,16 @@ export function SettingsPanel() {
|
||||
<p className="text-xs text-slate-500">AstroResearch 核心系统运行参数配置</p>
|
||||
</div>
|
||||
|
||||
<div className="glass p-6 rounded-2xl space-y-6">
|
||||
<div className="console-panel p-6 rounded-lg space-y-6">
|
||||
<div className="border-b border-slate-200 pb-4">
|
||||
<h3 className="text-sm font-semibold text-slate-800 mb-1">配置文件 (.env) 运作模式</h3>
|
||||
<p className="text-xs text-slate-500 leading-relaxed">
|
||||
依据实施方案,所有的 API 密钥及鉴权 Token 统一保存在项目根目录下的 <code className="text-xs text-purple-650 font-semibold bg-purple-50 px-1 py-0.5 rounded">.env</code> 配置文件中,避免在数据库内泄露。
|
||||
依据实施方案,所有的 API 密钥及鉴权 Token 统一保存在项目根目录下的 <code className="text-xs text-blueprint font-semibold bg-slate-50 border border-slate-200 px-1 py-0.5 rounded">.env</code> 配置文件中,避免在数据库内泄露。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-purple-50 border border-purple-200 p-4 rounded-xl text-xs text-slate-650 leading-relaxed">
|
||||
<strong>提示</strong> 系统在后端启动时,会自动创建本地文献库物理目录并向数据库及天文学字典导入缓存。如需使用天文学中英翻译修正,请确保本地 <code className="text-xs text-purple-650 font-semibold bg-purple-50 px-1 py-0.5 rounded">/home/fmq/下载/astrodict_241020/astrodict241020_ec.txt</code> 路径文件内容完整可用。
|
||||
<div className="bg-slate-50 border border-slate-200 border-l-4 border-l-blueprint p-4 rounded-md text-xs text-slate-700 leading-relaxed">
|
||||
<strong>提示</strong> 系统在后端启动时,会自动创建本地文献库物理目录并向数据库及天文学字典导入缓存。如需使用天文学中英翻译修正,请确保本地 <code className="text-xs text-blueprint font-semibold bg-slate-100 px-1 py-0.5 rounded">/home/fmq/下载/astrodict_241020/astrodict241020_ec.txt</code> 路径文件内容完整可用。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@ export function SyncPanel() {
|
||||
|
||||
{/* 全局错误显示 */}
|
||||
{state.errorMsg && (
|
||||
<div className="p-4 rounded-lg bg-red-50 border border-red-200 flex gap-3 text-xs text-red-750 items-start">
|
||||
<div className="p-4 rounded-md bg-rose-50 border border-rose-200/60 flex gap-3 text-xs text-rose-800 items-start">
|
||||
<AlertTriangle className="w-4 h-4 shrink-0 mt-0.5" />
|
||||
<div className="font-semibold">{state.errorMsg}</div>
|
||||
</div>
|
||||
@@ -71,10 +71,10 @@ export function SyncPanel() {
|
||||
/>
|
||||
|
||||
{/* 3. 常用批量同步检索配置 */}
|
||||
<div className="console-panel p-6 rounded-xl border border-slate-200 bg-white space-y-4 shadow-xs">
|
||||
<div className="console-panel p-6 rounded-lg border border-slate-200 bg-white space-y-4 shadow-sm">
|
||||
<div className="flex flex-col gap-1 border-b border-slate-100 pb-2 select-none">
|
||||
<h3 className="text-xs font-bold text-slate-900 flex items-center gap-2">
|
||||
<SlidersHorizontal className="w-4 h-4 text-sky-600" />
|
||||
<SlidersHorizontal className="w-4 h-4 text-slate-700" />
|
||||
<span>常用批量同步检索配置</span>
|
||||
</h3>
|
||||
<p className="text-slate-500 text-xs mt-0.5">
|
||||
@@ -83,7 +83,7 @@ export function SyncPanel() {
|
||||
</div>
|
||||
|
||||
{state.syncQueries.length === 0 ? (
|
||||
<div className="text-center py-6 text-xs text-slate-405 italic select-none">
|
||||
<div className="text-center py-6 text-xs text-slate-400 italic select-none">
|
||||
暂无已存检索配置。执行一次批量元数据同步后将自动去重记录在此。
|
||||
</div>
|
||||
) : (
|
||||
@@ -104,7 +104,7 @@ export function SyncPanel() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => state.handleReuseQuery(sq)}
|
||||
className="px-2.5 py-1.5 rounded-lg bg-slate-50 border border-slate-250 text-slate-650 hover:bg-slate-100 hover:text-slate-800 transition-all text-xs font-bold cursor-pointer"
|
||||
className="px-2.5 py-1.5 rounded-md bg-slate-50 border border-slate-250 text-slate-655 hover:bg-slate-100 hover:text-slate-800 transition-all text-xs font-bold cursor-pointer"
|
||||
>
|
||||
加载
|
||||
</button>
|
||||
@@ -112,14 +112,14 @@ export function SyncPanel() {
|
||||
type="button"
|
||||
disabled={state.status.active}
|
||||
onClick={() => state.handleQuickSync(sq)}
|
||||
className="px-2.5 py-1.5 rounded-lg bg-sky-50 border border-sky-200 text-sky-700 hover:bg-sky-100 transition-all text-xs font-bold cursor-pointer disabled:opacity-40"
|
||||
className="px-2.5 py-1.5 rounded-md bg-slate-100 border border-slate-250 text-slate-800 hover:bg-slate-200 transition-all text-xs font-bold cursor-pointer disabled:opacity-40"
|
||||
>
|
||||
一键同步
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => state.handleDeleteQuery(sq.id)}
|
||||
className="px-2.5 py-1.5 rounded-lg bg-red-50 border border-red-200 text-red-755 hover:bg-red-100 transition-all text-xs font-bold cursor-pointer"
|
||||
className="px-2.5 py-1.5 rounded-md bg-rose-50 border border-rose-200/60 text-rose-855 hover:bg-rose-100 transition-all text-xs font-bold cursor-pointer"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user