diff --git a/frontend-web/.env.development b/frontend-web/.env.development index 79771dc1..d5dfe0f9 100644 --- a/frontend-web/.env.development +++ b/frontend-web/.env.development @@ -27,15 +27,16 @@ VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=https://ch1.b.uat.4.cn/knowes/api/iframeKnowledgeUrl VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red VITE_AIPI_MOPECHAT_URL=https://ch1.b.uat.4.cn/likun_b1_mope -VITE_AIPI_BACKENP_URL=http://47.88.25.99:8000 -# 工作室/笔记本后端接口(侧边栏“工作室”列表、笔记本详情等都走这里,不是 VITE_NOTE_API_BASE_URL) -# 工作室/笔记本前端地址(用于 auth/callback 等跳转) +VITE_AIPI_BACKENP_URL=http://localhost:8003 +# 工作室/空间后端接口(侧边栏“工作室”列表、空间详情等都走这里,不是 VITE_NOTE_API_BASE_URL) +# 工作室/空间前端地址(用于 auth/callback 等跳转) # VITE_AIPI_FRONTEND_URL=https://knowledgetoolserver.b.uat.4.cn -VITE_NOTEBOOK_API_BASE_URL=http://47.88.25.99:8000 -# 工作室侧边栏显示开关;仅控制入口显示,展开内容仍依赖 notebook 接口成功返回数据 -VITE_STUDIO_ENABLED=true -VITE_AIPI_FRONTEND_URL=http://47.88.25.99:3000 +VITE_AIPI_FRONTEND_URL=http://localhost:3000 VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer +# A 系统跳转地址(人物/组织/事件等列表项打开链接) +VITE_AIPI_PRESENT_URL=https://present.a.uat.4.cn/present/domain +# B 系统知识库 iframe 根地址 +VITE_AIPI_KNOWES_URL=https://ch1.b.uat.4.cn/knowes # MaxKey 鉴权(getTokenByOnlineTicket) VITE_AUTH_BASE_URL=https://auth.y.sit.4.cn diff --git a/frontend-web/.env.development.local.example b/frontend-web/.env.development.local.example index 9a45a1fe..14201e1a 100644 --- a/frontend-web/.env.development.local.example +++ b/frontend-web/.env.development.local.example @@ -15,15 +15,17 @@ VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=https://ch1.b.uat.4.cn/knowes/api/iframeKnowledgeUrl VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red VITE_AIPI_MOPECHAT_URL=https://ch1.b.uat.4.cn/likun_b1_mope -# 工作室/笔记本后端接口(侧边栏“工作室”列表、笔记本详情等都走这里,不是 VITE_NOTE_API_BASE_URL) +# 工作室/空间后端接口(侧边栏“工作室”列表、空间详情等都走这里,不是 VITE_NOTE_API_BASE_URL) VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.uat.4.cn -# 工作室/笔记本前端地址(用于 auth/callback 等跳转) +# 工作室/空间前端地址(用于 auth/callback 等跳转) VITE_AIPI_FRONTEND_URL=https://knowledgetoolserver.b.uat.4.cn VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer +# A 系统跳转地址(人物/组织/事件等列表项打开链接) +VITE_AIPI_PRESENT_URL=http://present.a.uat.4.cn/present/domain +# B 系统知识库 iframe 根地址 +VITE_AIPI_KNOWES_URL=http://ch1.b.uat.4.cn/knowes VITE_AUTH_BASE_URL=https://auth.y.sit.4.cn VITE_AIPI_PGLINKTO_URL=https://pg.b.uat.4.cn VITE_AIPI_XKLINKTO_URL=https://kz.b.uat.4.cn # VITE_NOTE_API_BASE_URL=http://127.0.0.1:8000 -# 工作室侧边栏显示开关;仅控制入口显示,展开内容仍依赖 notebook 接口成功返回数据 -VITE_STUDIO_ENABLED=true diff --git a/frontend-web/.env.example b/frontend-web/.env.example index 9653a1c4..5939e301 100644 --- a/frontend-web/.env.example +++ b/frontend-web/.env.example @@ -23,7 +23,6 @@ # VITE_NOTE_API_BASE_URL=http://47.88.25.99:8000 # VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.uat.4.cn # VITE_AIPI_FRONTEND_URL=https://knowledgetoolserver.b.uat.4.cn -# VITE_STUDIO_ENABLED=true # 仅控制“工作室”入口显示,列表内容仍依赖 notebook 接口数据 # --- 生产打包(见 .env.production)--- # 部署前修改 .env.production 中的地址,再执行 pnpm build diff --git a/frontend-web/.env.network b/frontend-web/.env.network index 923b70c7..569b8422 100644 --- a/frontend-web/.env.network +++ b/frontend-web/.env.network @@ -28,14 +28,16 @@ VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=https://ch1.b.uat.4.cn/knowes/api/iframeKnowl VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red VITE_AIPI_MOPECHAT_URL=https://ch1.b.uat.4.cn/likun_b1_mope VITE_AIPI_BACKENP_URL=http://47.88.25.99:8000 -# 工作室/笔记本后端接口(侧边栏“工作室”列表、笔记本详情等都走这里,不是 VITE_NOTE_API_BASE_URL) -# 工作室/笔记本前端地址(用于 auth/callback 等跳转) +# 工作室/空间后端接口(侧边栏“工作室”列表、空间详情等都走这里,不是 VITE_NOTE_API_BASE_URL) +# 工作室/空间前端地址(用于 auth/callback 等跳转) # VITE_AIPI_FRONTEND_URL=https://knowledgetoolserver.b.uat.4.cn VITE_NOTEBOOK_API_BASE_URL=http://47.88.25.99:8000 -# 工作室侧边栏显示开关;仅控制入口显示,展开内容仍依赖 notebook 接口成功返回数据 -VITE_STUDIO_ENABLED=true VITE_AIPI_FRONTEND_URL=http://47.88.25.99:3000 VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer +# A 系统跳转地址(人物/组织/事件等列表项打开链接) +VITE_AIPI_PRESENT_URL=http://present.a.uat.4.cn/present/domain +# B 系统知识库 iframe 根地址 +VITE_AIPI_KNOWES_URL=http://ch1.b.uat.4.cn/knowes # MaxKey 鉴权(getTokenByOnlineTicket) VITE_AUTH_BASE_URL=https://auth.y.sit.4.cn diff --git a/frontend-web/.env.production b/frontend-web/.env.production index 73c97e3c..08bf2a70 100644 --- a/frontend-web/.env.production +++ b/frontend-web/.env.production @@ -24,18 +24,21 @@ VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=https://ch1.b.uat.4.cn/knowes/api/iframeKnowledgeUrl VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red VITE_AIPI_MOPECHAT_URL=https://ch1.b.uat.4.cn/likun_b1_mope -# 工作室/笔记本后端接口(侧边栏“工作室”列表、笔记本详情等都走这里,不是 VITE_NOTE_API_BASE_URL) +# 工作室/空间后端接口(侧边栏“工作室”列表、空间详情等都走这里,不是 VITE_NOTE_API_BASE_URL) VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.uat.4.cn -VITE_NOTEBOOK_API_BASE_URL=https://knowledgetoolserver.b.uat.4.cn -# 工作室侧边栏显示开关;仅控制入口显示,展开内容仍依赖 notebook 接口成功返回数据 -VITE_STUDIO_ENABLED=true -# 工作室/笔记本前端地址(用于 auth/callback 等跳转) -VITE_AIPI_FRONTEND_URL=https://knowledgetoolweb.b.uat.4.cn +# 我的空间前端 +VITE_AIPI_FRONTEND_URL= xxx VITE_AIPI_CONSUMER_URL=https://ch1.b.uat.4.cn/consumer - +# A 系统跳转地址(人物/组织/事件等列表项打开链接) +VITE_AIPI_PRESENT_URL=https://present.a.uat.4.cn/present/domain +# B 系统知识库 iframe 根地址 +VITE_AIPI_KNOWES_URL=https://ch1.b.uat.4.cn/knowes # MaxKey 鉴权(getTokenByOnlineTicket) VITE_AUTH_BASE_URL=https://auth.y.uat.4.cn VITE_AIPI_PGLINKTO_URL=https://pg.b.uat.4.cn VITE_AIPI_XKLINKTO_URL=https://kz.b.uat.4.cn +# 工作室/空间前端地址(用于 auth/callback 等跳转) +VITE_AIPI_FRONTEND_URL=https://knowledgetoolweb.b.uat.4.cn + # 可选:Canvas 独立 LangGraph 地址 # VITE_CANVAS_LANGGRAPH_BASE_URL= diff --git a/frontend-web/.env.production.local.example b/frontend-web/.env.production.local.example index f56448e0..30436901 100644 --- a/frontend-web/.env.production.local.example +++ b/frontend-web/.env.production.local.example @@ -5,4 +5,3 @@ # VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api # VITE_AIPI_BACKENP_URL=https://your-notebook-backend-host # VITE_AIPI_FRONTEND_URL=https://your-notebook-frontend-host -# VITE_STUDIO_ENABLED=true diff --git a/frontend-web/.env.sit b/frontend-web/.env.sit index 7faa0dbb..d4a4af7b 100644 --- a/frontend-web/.env.sit +++ b/frontend-web/.env.sit @@ -24,7 +24,7 @@ VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=https://ch1.b.sit.4.cn/knowes/api/iframeKnowledgeUrl VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red VITE_AIPI_MOPECHAT_URL=https://ch1.b.sit.4.cn/likun_b1_mope -# 工作室/笔记本后端接口(侧边栏“工作室”列表、笔记本详情等都走这里,不是 VITE_NOTE_API_BASE_URL) +# 工作室/空间后端接口(侧边栏“工作室”列表、空间详情等都走这里,不是 VITE_NOTE_API_BASE_URL) VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.sit.4.cn VITE_NOTEBOOK_API_BASE_URL=https://knowledgetoolserver.b.sit.4.cn # 工作室侧边栏显示开关;仅控制入口显示,展开内容仍依赖 notebook 接口成功返回数据 @@ -32,10 +32,16 @@ VITE_STUDIO_ENABLED=true # 工作室/笔记本前端地址(用于 auth/callback 等跳转) VITE_AIPI_FRONTEND_URL=https://knowledgetoolserver.b.sit.4.cn VITE_AIPI_CONSUMER_URL=https://ch1.b.sit.4.cn/consumer - +# A 系统跳转地址(人物/组织/事件等列表项打开链接) +VITE_AIPI_PRESENT_URL=http://present.a.sit.4.cn/present/domain +# B 系统知识库 iframe 根地址 +VITE_AIPI_KNOWES_URL=https://ch1.b.sit.4.cn/knowes # MaxKey 鉴权(getTokenByOnlineTicket) VITE_AUTH_BASE_URL=https://auth.y.sit.4.cn VITE_AIPI_PGLINKTO_URL=https://pg.b.sit.4.cn VITE_AIPI_XKLINKTO_URL=https://kz.b.sit.4.cn +# 工作室/空间前端地址(用于 auth/callback 等跳转) +VITE_AIPI_FRONTEND_URL=https://knowledgetoolserver.b.sit.4.cn + # 可选:Canvas 独立 LangGraph 地址 # VITE_CANVAS_LANGGRAPH_BASE_URL= diff --git a/frontend-web/package.json b/frontend-web/package.json index c74784bd..1b854aca 100644 --- a/frontend-web/package.json +++ b/frontend-web/package.json @@ -74,7 +74,7 @@ "hast": "^1.0.0", "katex": "^0.16.28", "lucide-react": "^0.562.0", - "mermaid": "11.14.0", + "mermaid": "^11.15.0", "motion": "^12.26.2", "nanoid": "^5.1.6", "next-themes": "^0.4.6", diff --git a/frontend-web/src/components/assistant-ui/document-upload-popup.tsx b/frontend-web/src/components/assistant-ui/document-upload-popup.tsx new file mode 100644 index 00000000..363866e6 --- /dev/null +++ b/frontend-web/src/components/assistant-ui/document-upload-popup.tsx @@ -0,0 +1,898 @@ +"use client"; + +import { + createContext, + type FC, + type ReactNode, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogTitle, +} from "@/components/ui/dialog"; +import { Textarea } from "@/components/ui/textarea"; +import { + Search, + Loader2, + Plus, + Trash2, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { toast } from "sonner"; +import { getStoredAuth } from "@/core/auth"; +import Empty from "./empty"; +import { NetworkActor } from "./network-actor"; + +// ── 适配层 ───────────────────────────────────────────────── + +function authenticatedFetch(url: string, init?: RequestInit) { + const token = getStoredAuth()?.access_token; + return fetch(url, { + ...init, + headers: { + ...(init?.headers ?? {}), + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + }); +} + +function getYUserId(): string { + if (typeof window === "undefined") return "1157761008018653184"; + try { + const raw = localStorage.getItem("deerflow.external_user_info"); + if (raw) { + const obj = JSON.parse(raw) as { yUserId?: string }; + if (obj?.yUserId) return obj.yUserId; + } + } catch { } + return "1157761008018653184"; +} + + + +const searchTypes = [ + { value: "human", label: "人物基因库" }, + { value: "organization", label: "组织基因库" }, + { value: "community", label: "群体基因库" }, + { value: "issue", label: "议题基因库" }, + { value: "event", label: "事件基因库" }, + { value: "threat", label: "风险与机会" }, +] as const; + +// ── Context ──────────────────────────────────────────────── +interface DocumentUploadDialogContextType { + openDialog: (initialSearchText?: string) => void; + closeDialog: ( + mode?: "search", + data?: { query: string; mode: string } + ) => Promise; + // 上传成功回调,返回上传的来源信息 + onUploadComplete?: (sources: Array<{ id?: number; title: string; task_id?: string; status: string }>) => void; +} + +// ── 任务轮询类型 ───────────────────────────────────────────── +interface TaskSource { + task_id: string; + title: string; + status: 'processing' | 'success' | 'error'; + source_type: string; +} + +interface UploadResponse { + status: string; + uploaded: number; + skipped: number; + sources: TaskSource[]; +} + +interface listDataItem { + id?: string | number; + name?: string; + redisKey?: string; + abstractz?: string; + checked: boolean; + title: string; + content: string; + url: string; + // 上传文件轮询相关字段 + task_id?: string; + processing_status?: 'processing' | 'success' | 'error'; + is_uploaded_file?: boolean; + country?: string; + dateOfBirth?: string; + occupationData?: string; + party?: string; + sexOrGender?: string; + imagePath?: string; + mongoId?: string; + countryData?: string; + countrySingle?: string; + subtype?: string; + text?: string; + twoLevel?: string; + account_ids?: unknown[]; + community_description?: string; + community_name?: string; + community_type?: string; + create_time?: string; + creator?: string; + groups?: string; + labels?: string; + nations?: string; + params?: string; + platforms?: string; + real_total?: number; + total?: number; + update_time?: string; + virtual_total?: number; + direction?: string; + hot_num?: number; + issue_hot?: number; + issue_name?: string; + issue_type?: string; + news_num?: number; + post_num?: number; + topic?: string; + abstract?: string; + allwords?: string[]; + category?: string; + controversy_num?: number; + date?: string; + discriminant_reason?: string; + discriminant_result?: string; + doc_author?: string; + doc_ids?: string[]; + emotion_info?: string; + first_publish_time?: string; + grade?: string; + human_id_list?: string; + human_list?: string[]; + if_artificial?: number; + if_follow?: number; + if_shehua?: number; + if_show?: number; + image_path?: string; + import_num?: number; + insert_time?: string; + jw?: string; + keywords?: string[]; + location?: string[]; + location_list?: unknown[]; + location_type?: number; + longitude?: number; + media_board?: string; + media_type?: string; + nowords?: unknown[]; + onewords?: unknown[]; + organization_id_list?: string; + organization_list?: unknown[]; + raw_id?: string; + report_num?: number; + site_name?: string; + source_type?: string; + sub_category?: string; + subject?: string; + super_category?: string; + surge_num?: number; + tone?: string; + topic_id?: string; + total_num?: number; + type?: string; + userid?: string; + wx_sub_score?: string; + wx_total_score?: string; +} + +const DocumentUploadDialogContext = + createContext(null); + +export const useDocumentUploadDialog = () => { + const context = useContext(DocumentUploadDialogContext); + if (!context) { + throw new Error( + "useDocumentUploadDialog must be used within DocumentUploadDialogProvider" + ); + } + return context; +}; + +// ── Provider ─────────────────────────────────────────────── +export const DocumentUploadDialogProvider: FC<{ + children: ReactNode; + onClose?: ( + mode?: "search", + data?: { query: string; mode: string } + ) => void | Promise; + searchSpace: { id: number | string;[key: string]: unknown }; + onUploadSuccess?: () => void; + onUploadComplete?: (sources: Array<{ id?: number; title: string; task_id?: string; status: string }>) => void; +}> = ({ children, onClose, searchSpace, onUploadSuccess, onUploadComplete }) => { + const [isOpen, setIsOpen] = useState(false); + const [initialSearchText, setInitialSearchText] = useState(""); + const isClosingRef = useRef(false); + + const openDialog = useCallback((initialSearchText?: string) => { + if (isClosingRef.current) return; + setInitialSearchText(initialSearchText || ""); + setIsOpen(true); + }, []); + + const closeDialog = useCallback( + async (mode?: "search", data?: { query: string; mode: string }) => { + isClosingRef.current = true; + if (onClose) { + try { + await onClose(mode, data); + } catch (error) { + console.error("Error in Provider onClose:", error); + } + } + setIsOpen(false); + setTimeout(() => { + isClosingRef.current = false; + }, 300); + }, + [onClose] + ); + + const handleOpenChange = useCallback( + (open: boolean) => { + if (!open) { + if (!isClosingRef.current) { + void closeDialog("search", { query: "", mode: "" }); + void closeDialog(); + } + } else { + if (!isClosingRef.current) { + setIsOpen(true); + } + } + }, + [closeDialog] + ); + + return ( + + {children} + + + ); +}; + +// ── Popup content ────────────────────────────────────────── +const UPLOAD_ACCEPT = + ".pdf,.docx,.xlsx,.pptx,.html,.csv,.md,.txt,.jpg,.jpeg,.png,.bmp,.webp,.tiff,.mp3,.mp4,.wav,.webm"; + +const DocumentUploadPopupContent: FC<{ + isOpen: boolean; + onOpenChange: (open: boolean) => void; + searchSpace: { id: number | string;[key: string]: unknown }; + onUploadSuccess?: () => void; + closeDialog: ( + mode?: "search", + data?: { query: string; mode: string } + ) => Promise; + initialSearchText?: string; +}> = ({ isOpen, onOpenChange, searchSpace, onUploadSuccess, initialSearchText }) => { + const { onUploadComplete } = useContext(DocumentUploadDialogContext) || {}; + const searchSpaceId = String(searchSpace?.id); + + // 从环境变量获取配置 + const backendUrl = import.meta.env.VITE_AIPI_BACKENP_URL || "http://localhost:8001"; + const consumerUrl = import.meta.env.VITE_AIPI_CONSUMER_URL || "http://ch1.b.uat.4.cn/consumer"; + const presentUrl = import.meta.env.VITE_AIPI_PRESENT_URL || "http://present.a.uat.4.cn/present/domain"; + const knowesUrl = import.meta.env.VITE_AIPI_KNOWES_URL || "http://ch1.b.uat.4.cn/knowes"; + const knowledgeUrl = import.meta.env.VITE_AIPI_KNOWLEDGE_URL || "http://chlib.b.uat.4.cn/knowlibrary"; + + const [searchText, setSearchText] = useState(""); + const [searchValue, setSearchValue] = useState(""); + const [activeTab, setActiveTab] = useState<"search" | "Y" | "upload" | "A" | "actor">("search"); + const [isSearching, setIsSearching] = useState(false); + const [isImporting, setIsImporting] = useState(false); + const [listData, setListData] = useState([]); + const [searchType, setSearchType] = useState("human"); + const [selectedSearchSources, setSelectedSearchSources] = useState([]); + // upload tab state + const fileInputRef = useRef(null); + const [uploading, setUploading] = useState(false); + + useEffect(() => { + if (isOpen && initialSearchText) { + setSearchText(initialSearchText); + setSearchValue(initialSearchText); + } + }, [isOpen, initialSearchText]); + + if (!searchSpaceId) return null; + + const handleSuccess = () => { + onUploadSuccess?.(); + onOpenChange(false); + }; + + const searchActiveChange = async (type: "search" | "Y" | "upload" | "A" | "actor") => { + setActiveTab(type); + if (type === "A") { + await searchAChange(searchType); + } + }; + + const handleFileUpload = async (files: File[]) => { + if (!files.length) return; + setUploading(true); + try { + const token = getStoredAuth()?.access_token ?? ""; + const formData = new FormData(); + formData.append("search_space_id", searchSpaceId); + for (const file of files) { + formData.append("files", file); + } + const res = await fetch(`${backendUrl}/api/v1/documents/fileupload`, { + method: "POST", + headers: { Authorization: `Bearer ${token}` }, + body: formData, + }); + if (res.ok) { + const data = await res.json() as UploadResponse; + // 调用上传完成回调,传递来源信息 + if (data.sources && data.sources.length > 0) { + onUploadComplete?.(data.sources.map(s => ({ + id: undefined, // 后端返回的数据中暂时没有 id + title: s.title, + task_id: s.task_id, + status: s.status, + }))); + } + toast.success(`上传成功 ${data.uploaded || files.length} 个文件`); + // 只关闭上传弹窗 + onOpenChange(false); + } else { + toast.error("上传失败,请重试"); + } + } catch { + toast.error("上传失败,请重试"); + } finally { + setUploading(false); + if (fileInputRef.current) fileInputRef.current.value = ""; + } + }; + + const handleImport = async () => { + if (selectedSearchSources.length === 0) { + toast.warning("没有可导入的来源"); + return; + } + setIsImporting(true); + const dataList = selectedSearchSources.map((item) => ({ + title: item.title, + content: item.content, + url: item.url, + })); + try { + const response = await authenticatedFetch(`${backendUrl}/api/v1/source/import`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + search_space_id: Number(searchSpaceId), + source_list: dataList, + }), + }); + if (response.ok) { + const data = await response.json() as { imported_count?: number }; + toast.success(`成功导入 ${data.imported_count || dataList.length} 个来源`); + setListData([]); + handleBatchDelete(); + } else { + const errorData = await response.json() as { detail?: string }; + toast.error(errorData.detail || "导入失败,请重试"); + } + } catch { + toast.error("导入失败,请重试"); + } finally { + setIsImporting(false); + } + }; + + const listDataCheckedChange = (index: number, checked: boolean) => { + setListData((prevSources) => { + const newSources = prevSources.map((item, aIndex) => + aIndex === index ? { ...item, checked } : item + ); + setSelectedSearchSources(newSources.filter((item) => item.checked)); + return newSources; + }); + }; + + const handleBatchDelete = () => { + setListData((prev) => prev.map((item) => ({ ...item, checked: false }))); + setSelectedSearchSources([]); + }; + + + + function urlSafeBtoa(str: string): string { + const encoder = new TextEncoder(); + const uint8Array = encoder.encode(str); + const binaryString = Array.prototype.map + .call(uint8Array, (byte: number) => String.fromCharCode(byte)) + .join(""); + return btoa(binaryString).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); + } + + const linkToA = (url: string) => { window.open(url); }; + + const queryTopicTargetHumanList = async () => { + setIsSearching(true); + const response = await authenticatedFetch(`${consumerUrl}/topicQuery/queryTopicTargetHumanList`, { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: "Bearer admin" }, + body: JSON.stringify({ name: searchText, page: 1, size: 50 }), + }); + if (response.ok) { + const data = await response.json() as { data: listDataItem[] }; + setIsSearching(false); + const result = data.data; + result.forEach((item) => { + item.checked = false; + item.title = item.name || ""; + item.content = item.abstractz || ""; + const redirect = urlSafeBtoa(`/gene/#/human/basic?mid=${item.id}`); + item.url = `${presentUrl}?inredirect=${redirect}`; + }); + setListData(result); + } + setIsSearching(false); + }; + + const organizationSearch = async () => { + setIsSearching(true); + const response = await authenticatedFetch(`${consumerUrl}/topicQuery/queryCommunity`, { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: "Bearer admin" }, + body: JSON.stringify({ name: searchText, page: 1, size: 50 }), + }); + if (response.ok) { + const data = await response.json() as { data: listDataItem[] }; + setIsSearching(false); + const result = data.data; + result.forEach((item) => { + item.checked = false; + item.title = item.name || ""; + item.content = item.abstractz || ""; + const redirect = urlSafeBtoa(`/gene/#/organization/basicAttr?mid=${item.mongoId}`); + item.url = `${presentUrl}?inredirect=${redirect}`; + }); + setListData(result); + } + setIsSearching(false); + }; + + const queryCommunityList = async () => { + setIsSearching(true); + const params = { keyword: searchText, page: 1, size: 50 }; + const queryString = Object.entries(params).map(([k, v]) => `${k}=${v}`).join("&"); + const response = await authenticatedFetch(`${consumerUrl}/topicQuery/queryCommunityList?${queryString}`, { + method: "GET", + headers: { "Content-Type": "application/json", Authorization: "Bearer admin" }, + }); + if (response.ok) { + const data = await response.json() as { data: listDataItem[] }; + setIsSearching(false); + const result = data.data; + result.forEach((item) => { + item.checked = false; + item.title = item.community_name || ""; + item.content = item.community_description || ""; + const redirect = urlSafeBtoa(`/gene/#/commune/basicCommune?cid=${item.id}`); + item.url = `${presentUrl}?inredirect=${redirect}`; + }); + setListData(result); + } + setIsSearching(false); + }; + + const issueSearch = async () => { + setIsSearching(true); + const response = await authenticatedFetch(`${consumerUrl}/issue/list?keyword=${searchText}`, { + method: "GET", + headers: { "Content-Type": "application/json", Authorization: "Bearer admin" }, + }); + if (response.ok) { + const data = await response.json() as { data: listDataItem[] }; + setIsSearching(false); + const result = data.data; + result.forEach((item) => { + item.checked = false; + item.title = item.issue_name || ""; + item.content = item.issue_type || ""; + const redirect = encodeURIComponent(`/gene/#/issue/zzyyIssue?mid=${item.id}&event_type=issue`); + item.url = `${presentUrl}?inredirect=${redirect}`; + }); + setListData(result); + } + setIsSearching(false); + }; + + const eventSearch = async () => { + setIsSearching(true); + const response = await authenticatedFetch(`${consumerUrl}/event/queryEvents`, { + method: "POST", + headers: { "Content-Type": "application/json", Authorization: "Bearer admin" }, + body: JSON.stringify({ word: searchText, page: 1, size: 50, type: "hot", timeRange: "" }), + }); + if (response.ok) { + const data = await response.json() as { data: listDataItem[] }; + setIsSearching(false); + const result = data.data; + result.forEach((item) => { + item.checked = false; + const redirect = urlSafeBtoa(`/gene/#/event/basicEvent?event_type=hot&mid=${item.id}`); + item.url = `${presentUrl}?inredirect=${redirect}`; + }); + setListData(result); + } + setIsSearching(false); + }; + + const topicQuery = async () => { + setIsSearching(true); + let result: listDataItem[] = []; + for (const discriminant of ["威胁", "ZJ机"]) { + const queryString = `keyword=${searchText}&discriminant=${discriminant}`; + const response = await authenticatedFetch(`${consumerUrl}/topicQuery/queryEventFromA?${queryString}`, { + method: "GET", + headers: { "Content-Type": "application/json", Authorization: "Bearer admin" }, + }); + if (response.ok) { + const data = await response.json() as { data: listDataItem[] }; + const results = data.data; + results.forEach((item) => { + item.checked = false; + const redirect = urlSafeBtoa(`/gene/#/event/basicEvent?event_type=hot&mid=${item.id}`); + item.url = `${presentUrl}?inredirect=${redirect}`; + }); + result = [...result, ...results]; + } + } + setIsSearching(false); + setListData(result); + }; + + const searchAChange = async (type: string) => { + if (type === "human") await queryTopicTargetHumanList(); + else if (type === "organization") await organizationSearch(); + else if (type === "community") await queryCommunityList(); + else if (type === "issue") await issueSearch(); + else if (type === "event") await eventSearch(); + else if (type === "threat") await topicQuery(); + }; + + const searchTypeChange = (value: string) => { + setSearchType(value); + setListData([]); + void searchAChange(value); + }; + + const searchDataChange = () => { + setIsSearching(true); + setSearchValue(searchText); + setTimeout(() => setIsSearching(false), 500); + if (activeTab === "A") void searchAChange(searchType); + }; + + const yUserId = getYUserId(); + + return ( + + e.preventDefault()} + onInteractOutside={(e) => e.preventDefault()} + > + 添加来源 + 从外部系统搜索并导入来源 +
+
+
+ {/* 搜索框 */} +
+