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:
fmq
2026-06-27 09:56:36 +08:00
parent 5db4cc5998
commit 5f2d2d83f6
62 changed files with 6584 additions and 6308 deletions
-184
View File
@@ -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);
}
}
+1 -1
View File
@@ -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));
+5 -5
View File
@@ -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'
}`}
>
+117
View File
@@ -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}
+6 -6
View File
@@ -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>
);
}
+7 -7
View File
@@ -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
View File
@@ -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;
}
+16 -16
View File
@@ -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>
+84 -102
View File
@@ -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>
)}
+1 -1
View File
@@ -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}
+65 -81
View File
@@ -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>
+4 -4
View File
@@ -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>
+7 -7
View File
@@ -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>