Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ export const en = {
legendNone: "No class matches",
legendOnly: "Only",
legendShowAll: (n: number) => `Show all (${n} hidden)`,
legendHideAll: "Hide all",
legendAllHint:
"Every class on screen, most common first. Click to show or hide.",
searchMore: (n: number) => `${n} more — load 20`,
Expand Down
1 change: 1 addition & 0 deletions web/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ export const zh: Strings = {
legendNone: "没有匹配的类",
legendOnly: "只看",
legendShowAll: (n: number) => `显示全部(隐藏了 ${n} 个)`,
legendHideAll: "全部隐藏",
legendAllHint: "画面上的全部类,按出现次数排。点一下显示或隐藏。",
searchMore: (n: number) => `还有 ${n} 个——再加载 20`,
zoomIn: "放大",
Expand Down
127 changes: 80 additions & 47 deletions web/src/pages/Graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { NextStep, nextStep, useReadiness } from "./NextStep";
import {
ArrowLeft,
ArrowRight,
ChevronDown,
ChevronRight,
CircleDashed,
ExternalLink,
Expand Down Expand Up @@ -86,7 +87,7 @@ import {
LinkButton,
Pill,
REVEAL,
ROW_TRAILING,
ROW_VALUE,
Row,
Segmented,
ToolButton,
Expand Down Expand Up @@ -1276,13 +1277,6 @@ export function Graph() {

{/* chip 上的数是**全部类**,不是被收起来的那几个——
点开看到的就是全部(搜得到任何一个),写「+3」等于承诺了另一件事 */}
{/* 复位。**只要存在隐藏就给一步到位的出口**——「只看」很容易把
画面收得很窄,没有这个就得挨个点回来 */}
{hiddenTypes.size > 0 && (
<Pill onClick={() => setHiddenTypes(new Set())}>
{S.graph.legendShowAll(hiddenTypes.size)}
</Pill>
)}

{legendRest.length > 0 && (
<div className="relative" ref={legendPop.rootRef}>
Expand All @@ -1305,26 +1299,60 @@ export function Graph() {
{legendPop.open && (
<div
ref={legendPop.panelRef}
className="u-menu-glass absolute left-0 top-0 z-50 w-64 overflow-hidden rounded-overlay p-2 shadow-2xl"
className="u-menu-glass absolute left-0 top-0 z-50 w-72 overflow-hidden rounded-overlay shadow-2xl"
>
{/* 面板盖在 chip 原位,所以**第一行就长成那个 chip 的样子**,
点它收回去——「哪儿展开的就从哪儿收回去」,
与通知/用户卡片的关闭键跟触发键原位重合是同一个道理 */}
<Pill className="mb-2 w-full" onClick={() => legendPop.close()}>
{S.graph.legendMore(types.length)}
<X size={11} className="ml-auto text-ink-2" />
</Pill>
<Input
size="sm"
autoFocus
value={legendQ}
onChange={(e) => setLegendQ(e.target.value)}
placeholder={S.graph.legendSearch}
className="mb-2 w-full"
/>
{/* 与库切换器、告警面板、用户菜单同一副解剖:第一行是触发它的
那个胶囊自己,三角翻上去,点它缩回;没有浮在角上的关闭叉
——「哪儿展开的就从哪儿收回去」 */}
<div
onClick={() => legendPop.close()}
className="u-row-shell flex cursor-pointer items-center gap-3 border-b border-line px-4 py-3"
>
<span className="min-w-0 flex-1 truncate text-body font-medium text-ink">
{S.graph.legendMore(types.length)}
</span>
<ChevronDown
size={12}
className="shrink-0 rotate-180 text-ink-2"
/>
</div>
{/* 全开 / 全关。**从顶栏那枚独立胶囊搬进来的**:它只在有隐藏时
才出现,于是那一排的宽度会随着你点类跳来跳去;而它要做的事
(«把画面收窄»的反面)本就属于这份清单,不属于清单外面。
两个都常驻、不可用时置灰——一个会消失的出口,第二次要用时
得先想起它长在哪儿 */}
<div className="flex items-center gap-2 border-b border-line px-4 py-2">
<LinkButton
disabled={hiddenTypes.size === 0}
onClick={() => setHiddenTypes(new Set())}
>
{S.graph.legendShowAll(hiddenTypes.size)}
</LinkButton>
<LinkButton
className="ml-auto"
disabled={hiddenTypes.size === types.length}
onClick={() =>
setHiddenTypes(new Set(types.map(([k]) => k)))
}
>
{S.graph.legendHideAll}
</LinkButton>
</div>
{/* 查找:没有自己的框(bare)——它是面板的一段,不是面板里
摆的一个控件,与库切换器的查找同一个做法 */}
<div className="border-b border-line px-4 py-3">
<Input
bare
autoFocus
value={legendQ}
onChange={(e) => setLegendQ(e.target.value)}
placeholder={S.graph.legendSearch}
className="w-full text-body"
/>
</div>
{/* **列的是全部类,不只是收起来的那些**:想找一个类的时候,
没人记得它是不是恰好排进了前几个 */}
<div className="flex max-h-64 flex-col overflow-y-auto">
<div className="u-scroll flex max-h-64 flex-col overflow-y-auto px-2 py-1">
{types
.filter(([, t]) =>
t.label.toLowerCase().includes(legendQ.toLowerCase()),
Expand Down Expand Up @@ -3005,8 +3033,6 @@ function FactRow({
}) {
const [editing, setEditing] = useState(false);
const interval = fmtInterval(fact);
// 与 Review 的低置信口径一致:只有低到需要怀疑才挂 chip,常规置信保持沉默
const lowConfidence = fact.confidence < 0.75;
const go = fact.other_id ? () => onNavigate(fact.other_id!) : undefined;

return (
Expand Down Expand Up @@ -3034,11 +3060,14 @@ function FactRow({
{dir === "out" ? <ArrowRight size={12} /> : <ArrowLeft size={12} />}
</span>
<span className="min-w-0 flex-1">
{/* 第一行:谓词 + 宾语,读出来就是这条事实本身 */}
{/* 第一行:谓词 + 宾语,读出来就是这条事实本身。
**宾语紧跟着谓词**,不推到右边——主语是面板上那个实体,这一行
是它后半句话;把宾语顶到行尾,中间隔一整行空白,两个词就不再
读成一句了。谓词按内容占位、放不下才收,剩下的归宾语 */}
<span className="flex items-center gap-2">
<span
className={cn(
"min-w-0 flex-1 truncate text-body text-ink",
"min-w-0 truncate text-body text-ink",
fact.predicate_label === null && "italic text-ink-2",
)}
// 谓词还是可能长到放不下(`publishingPrinciples`)——悬停给全名,
Expand All @@ -3053,34 +3082,32 @@ function FactRow({
>
{fact.predicate_label ?? S.graph.unknownPredicate}
</span>
<span className={cn(ROW_TRAILING, "max-w-40 truncate")}>
<span className={ROW_VALUE}>
{fact.other_name ?? fmtObjectValue(fact.object_value) ?? "?"}
</span>
</span>
{/* 第二行:何时成立、成色如何、凭什么、以及改期的入口 */}
{/* 第二行:何时成立、要不要留神、以及看证据与改期的入口。
**没有日期也要说一句**——空着的时候,「原文没写日期」和
「有日期只是我没显示」在界面上长得一模一样,而这个产品的全部
重点就是时间。
置信度与「区间是对账闭合的」那个 ⟲ 都撤了:一个是数字、一个是
没人猜得出的符号,两者都只在这一行占位,说不清事。它们在证据
那一档里用整句话说得明白(见 EvidenceList) */}
<span className="flex items-center gap-2">
{interval && (
<span className="u-num text-fine text-ink-2">{interval}</span>
)}
{lowConfidence && (
<span className="shrink-0 u-num u-meta-warn text-fine">
{Math.round(fact.confidence * 100)}%
</span>
)}
<span
className={cn(
"u-num text-fine",
interval ? "text-ink-2" : "italic text-ink-2",
)}
>
{interval || S.graph.undated}
</span>
{fact.stale && (
<span className="u-chip u-chip-neutral shrink-0 !text-fine !px-2">
{S.graph.staleFactChip}
</span>
)}
{fact.contested && <ContestedChip kbId={kbId} c={fact.contested} />}
{fact.corrected && (
<span
className="shrink-0 text-fine text-ink-2"
title={S.graph.correctedHint}
>
⟲
</span>
)}
<span className="ml-auto flex shrink-0 items-center gap-2 pl-2">
{fact.evidence_count > 0 && (
<LinkButton
Expand Down Expand Up @@ -3202,6 +3229,12 @@ function EvidenceList({ kbId, fact }: { kbId: string; fact: EntityFact }) {
{evidence.data?.evidence.length === 0 && (
<p className="text-small text-ink-2">{S.graph.noEvidence}</p>
)}
{/* 这条区间不是原文写的,是引擎对账或人工裁决闭合的。**从事实行搬到这里**:
在行上它是一个 ⟲,谁也猜不出是什么意思;证据这一档本来就在回答
「凭什么这么说」,一句话说得明白 */}
{fact.corrected && (
<p className="text-fine text-ink-2">{S.graph.correctedHint}</p>
)}
{/* 置信度只在低到值得怀疑时说话(与 Review 低置信口径一致),常规不标 */}
{fact.confidence < 0.75 && (
<p className="text-fine text-warn">
Expand Down
38 changes: 28 additions & 10 deletions web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@
/* 动效两档:进入 fast、离开 base(见 .glass-strong 上的说明) */
--u-fast: 120ms;
--u-base: 260ms;
--u-surface-strong: rgba(10, 10, 10, 0.72);
/* 浮在画布上的那些面:顶栏、导航、时间岛、控件塔、停靠面板、所有弹层。
**0.72 → 0.68**:原先几乎是一块实底,浮在图上像贴了张纸,而这一层
本该让人隐约看得见它盖住了什么。一度调到 0.62,太轻——底下的图透过来
开始跟面板上的字抢注意力,这一层是拿来读的 */
--u-surface-strong: rgba(10, 10, 10, 0.68);
/* 指针停在面上时的实底。**毛玻璃是给"没在看它"的时候用的**——
半透让浮层不至于把画布切断;可一旦人把指针移进来,他要读的就是面板本身,
这时候背后的图反而是噪声。所以这两档不是审美偏好,是同一块面在
Expand Down Expand Up @@ -154,17 +158,23 @@ body::before {
`glass-strong border-l-0` 这种写法里 border-l-0 是输的——左栏四边都有
1px 边框,内容整体偏 1px,两个页面左上角本该重合的输入框差一像素。 */
@layer components {
/* **backdrop 要去色**。这一层的底色本身是纯中性的(rgba(10,10,10)、
白 2.5%,三个通道相等),可 backdrop-filter 把背后的画布糊上来一起显示——
图上的节点是有类型色的,蓝紫一片时透上来就是一块偏蓝的面。
调得更透只会让它更蓝:透上来的正是那些颜色。所以先把透上来的东西
去掉饱和,再谈透明度。这也正是「chrome 零色偏、彩色只属于数据」
那条原则该管到的地方——面板是 chrome,它不该跟着背后画的是什么变颜色 */
.glass {
background: var(--u-surface);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
backdrop-filter: blur(14px) saturate(0.3);
-webkit-backdrop-filter: blur(14px) saturate(0.3);
border: 1px solid var(--u-line);
}

.glass-strong {
background: var(--u-surface-strong);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px) saturate(0.3);
-webkit-backdrop-filter: blur(20px) saturate(0.3);
border: 1px solid var(--u-line);
/* 基态上的时长是**离开**时用的:慢一点,指针擦过边缘不会闪。
进入的时长写在 :hover 里,更快,为的是跟手 */
Expand Down Expand Up @@ -364,13 +374,15 @@ body::before {
CSS 只负责面板的皮 */
.u-menu-glass {
/* 中性,不掺色相:原本 rgba(14,14,16) 蓝比红绿高 2。这个面同时用在
通知、用户菜单、图例的「+N 个类」和 Inference 面板上,一处偏色四处都偏 */
通知、用户菜单、图例的「+N 个类」和 Inference 面板上,一处偏色四处都偏。
底色中性还不够——backdrop 把背后糊上来的东西是有颜色的(图例这一枚正压在
图最密的地方),所以那一层也去掉饱和,与 .glass / .glass-strong 同一条规矩 */
background: var(--u-menu-surface);
/* 基态上的时长是**离开**时用的:慢一点,指针擦过边缘不会闪。
进入的时长写在 :hover 里,更快,为的是跟手 */
transition: background-color var(--u-base) ease;
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
backdrop-filter: blur(24px) saturate(0.3);
-webkit-backdrop-filter: blur(24px) saturate(0.3);
border: 1px solid rgba(255, 255, 255, 0.14);
}
.u-menu-glass:hover {
Expand Down Expand Up @@ -613,8 +625,10 @@ select.input-dark option {
color: var(--u-text-2);
background: var(--u-surface);
border: 1px solid var(--u-line);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
/* backdrop 去色,与 .glass / .glass-strong / .u-menu-glass 同一条规矩:
这几枚药丸就浮在图上,背后是什么颜色它就跟着染什么颜色 */
backdrop-filter: blur(14px) saturate(0.3);
-webkit-backdrop-filter: blur(14px) saturate(0.3);
transition:
color var(--u-fast),
opacity var(--u-fast);
Expand All @@ -631,8 +645,12 @@ select.input-dark option {
.u-pill.is-static:hover {
color: var(--u-text-2);
}
/* 关掉的那一类:压到三成五**再划一道**。只靠淡,一排药丸里读出来是"这个不重要"
而不是"这个被关了";弹层里那份清单早就是划线的(line-through),两处说的是
同一个状态,不该长成两个样子 */
.u-pill.is-dim {
opacity: 0.35;
text-decoration: line-through;
}
.u-pill:focus-visible {
outline: 2px solid var(--u-ring);
Expand Down
5 changes: 5 additions & 0 deletions web/src/ui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1201,6 +1201,11 @@ export function rowClass(
}
/** 行右端小字:小一档、淡一档,整行被指着时跟着提亮 */
export const ROW_TRAILING = "ml-auto shrink-0 text-fine text-ink-2 group-hover:text-ink";
/** 紧跟在标签后面的值——与 `ROW_TRAILING` 同一副颜色,但**不推到右边**。
* 一左一右适合"名字 …… 数量"这种两栏读法;「谓词 宾语」是一句话,中间隔一
* 整行空白就读不成句子了 */
export const ROW_VALUE =
"min-w-0 flex-1 truncate text-fine text-ink-2 group-hover:text-ink";

export function Row({
active,
Expand Down