AstroResearch/dashboard/src/components/sync/BookmarkletToolCard.tsx
Asfmq 5db4cc5998 refactor: 全栈架构重构与质量硬化——API 错误统一、工具域重组、安全加固、前端组件化
后端核心变更:
  - API 层: 新增 AppError 枚举统一错误类型,替代散落的 (StatusCode, String)
  - Agent 工具域: 重组为 astro/system/ 和 astro/research/ 两级域,新增 ProcessPaperTool 流水线工具
  - 安全: 新增 SSRF 双层防护 (同步字符串级 + 异步 DNS 解析级),覆盖 IPv4/IPv6 私网段
  - 弱密码检测: 扩展弱密码列表并增加最小长度检查
  - LLM 客户端: 新增 ChatCompleter/Embedder trait,支持依赖注入与批量向量化 embed_batch
  - 批量处理: AssetBatch 从串行改为 Semaphore 并发池 (BATCH_CONCURRENCY=3)
  - 分块器: 重写为三阶段结构化管线 (章节解析→短节合并→带标题路径子块)
  - RAG: embedding 计算移出事务,RetrievalResult 新增 headings/section_index 字段
  - 检索: ADS/arXiv 并行检索 (tokio::join!),去重改用 HashSet,本地库回填批量 IN 查询
  - 天体查询: Sesame API 升级到 v4,新增视差误差/自行/视向速度/多波段测光字段
  - 迁移: 14 个增量文件合并为单一 init.sql,支持 sqlx::migrate! 内存库集成测试
  - 测试: circuit_breaker/hooks/task_board/session/memory/streaming_executor 新增修正 15+ 测试

  前端架构重构:
  - 目录重组: features/ → pages/ + components/ + hooks/ 三层分离
  - App.tsx 从 1181 行压缩至 ~174 行 (逻辑抽入 9 个自定义 Hook)
  - Agent 面板拆分为 AgentSessionSidebar/AgentMessageList/AgentInputArea 子组件
  - 新增 GlobalDialog/PaperDetailModal/UncachedPaperModal 通用对话框组件
  - 工具函数抽取: celestial.ts (天体坐标格式), paper.tsx (文献信息渲染)
2026-06-25 23:45:37 +08:00

75 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// dashboard/src/components/sync/BookmarkletToolCard.tsx
import { useRef, useEffect } from 'react';
// 书签代码常量
const BOOKMARKLET_CODE = `javascript:(async function(){try{let defaultBib='';try{const res=await fetch('http://localhost:8000/api/active_bibcode',{credentials:'include'});if(res.ok){const data=await res.json();if(data&&data.bibcode)defaultBib=data.bibcode;}}catch(e){}const b=prompt('请输入文献的 Bibcode / doi / arxiv_id :',defaultBib);if(!b||!b.trim())return;const bib=b.trim();if(window.location.protocol==='file:'){alert('[ERR] 浏览器安全策略限制:书签脚本无法直接读取本地磁盘 file://。\\n\\n提示对于本地 PDF/HTML 文件,请直接在 AstroResearch 的文献详情页点击“上传 PDF/HTML”按钮导入。');return;}let blob,type='html',ext='.html';const isPDF=document.contentType==='application/pdf'||window.location.pathname.toLowerCase().endsWith('.pdf')||document.title.toLowerCase().endsWith('.pdf');if(isPDF){try{const res=await fetch(window.location.href);if(!res.ok)throw new Error('HTTP '+res.status);blob=await res.blob();type='pdf';ext='.pdf';}catch(err){alert('[ERR] 无法读取该 PDF 数据。\\n(错误: '+err.message+')');return;}}else{blob=new Blob([document.documentElement.outerHTML],{type:'text/html'});}const fd=new FormData();fd.append('bibcode',bib);fd.append('type',type);fd.append('file',blob,bib+ext);const r=await fetch('http://localhost:8000/api/upload',{method:'POST',body:fd,credentials:'include'});if(r.ok){const d=await r.json();alert('[OK] '+(d.title||bib));}else{const t=await r.text();alert('[ERR '+r.status+'] '+t);}}catch(e){alert('[FAIL] '+e.message);}})();void(0);`;
export function BookmarkletToolCard() {
const linkRef = useRef<HTMLAnchorElement | null>(null);
useEffect(() => {
if (linkRef.current) {
linkRef.current.setAttribute('href', BOOKMARKLET_CODE);
}
}, []);
const handleCopyCode = () => {
navigator.clipboard.writeText(BOOKMARKLET_CODE);
alert('书签代码已成功复制到剪贴板!');
};
return (
<div className="console-panel p-6 rounded-xl border border-slate-200 bg-white space-y-4 shadow-xs">
<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></span>
</h3>
<p className="text-slate-500 text-xs mt-1 leading-relaxed">
Cloudflare/WAF PDF/HTML AstroResearch
</p>
</div>
<div className="space-y-3.5 text-xs text-slate-700">
<div className="space-y-1.5 font-bold select-none">
<span className="text-slate-500"></span>
<p className="text-[11px] text-slate-400 font-normal mt-1 leading-relaxed">
URL JavaScript
</p>
<div className="flex flex-wrap items-center gap-3 mt-2 select-none">
{/* 可直接拖拽的链接按钮 */}
<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"
title="拖拽我到您的书签栏中"
onClick={(e) => e.preventDefault()}
>
<span style={{ position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', opacity: 0, pointerEvents: 'none' }}>
AstroResearch
</span>
<span></span>
</a>
<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"
>
JavaScript
</button>
</div>
</div>
<div className="space-y-1.5 font-bold mt-4 select-none">
<span className="text-slate-500">使</span>
<ol className="list-decimal list-inside text-[11px] text-slate-500 font-normal pl-1 space-y-1.5 mt-1 leading-relaxed">
<li> <span className="font-bold text-slate-750">BIBCODE / DOI / arXiv ID </span></li>
<li><span className="font-bold text-slate-750"></span></li>
<li><span className="font-bold text-slate-750"> Bibcode</span></li>
</ol>
</div>
</div>
</div>
);
}