Files
2026-07-12 21:26:08 +08:00

1109 lines
34 KiB
HTML
Raw Permalink 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.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PR82 统一社区抽屉重设计</title>
<style>
:root {
--app-bg: #fdfaf2;
--app-surface: #fffdf7;
--app-raised: #f4ede0;
--app-fg: #3a3530;
--app-muted: #8a8175;
--app-faint: #b3a48c;
--app-border: #ece3d2;
--app-border-strong: #e1d5bf;
--app-accent: #e07a5f;
--app-accent-deep: #c0573f;
--app-accent-soft: #fff1ef;
--app-hover: rgba(224, 122, 95, 0.07);
--app-success: #7ea868;
--app-warn: #c98e3a;
--paper-grain:
radial-gradient(circle at 18% 20%, rgba(125, 94, 52, 0.035) 0 1px, transparent 1px),
radial-gradient(circle at 68% 42%, rgba(125, 94, 52, 0.03) 0 1px, transparent 1px);
--shadow: 0 1px 2px rgba(70, 55, 40, 0.05), 0 8px 18px rgba(70, 55, 40, 0.06);
--shadow-lg: 0 8px 24px rgba(70, 55, 40, 0.10), 0 28px 70px rgba(70, 55, 40, 0.14);
font-family: -apple-system, "SF Pro Text", "PingFang SC", "Noto Sans SC", sans-serif;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
overflow: hidden;
background:
radial-gradient(140% 95% at 50% -25%, rgba(255, 251, 240, 0.78), rgba(255, 251, 240, 0) 55%),
radial-gradient(ellipse 105% 92% at 50% 40%, rgba(255, 255, 255, 0) 52%, rgba(120, 98, 70, 0.05) 100%),
var(--app-bg);
color: var(--app-fg);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
button,
textarea {
font: inherit;
}
button {
cursor: pointer;
}
.prototype {
display: grid;
grid-template-rows: auto 1fr;
height: 100vh;
min-height: 720px;
}
.review-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
min-height: 58px;
padding: 0 20px;
border-bottom: 1px solid rgba(120, 98, 70, 0.12);
background: rgba(255, 253, 247, 0.86);
backdrop-filter: blur(16px);
}
.review-title {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.review-title strong {
overflow: hidden;
font-size: 15px;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.review-title span {
color: var(--app-muted);
font-size: 12px;
font-weight: 650;
}
.review-controls {
display: flex;
align-items: center;
gap: 10px;
}
.segmented {
display: inline-flex;
gap: 3px;
padding: 3px;
border: 1px solid var(--app-border);
border-radius: 10px;
background: rgba(255, 253, 247, 0.72);
}
.segmented button {
min-height: 30px;
padding: 0 10px;
border: 0;
border-radius: 7px;
background: transparent;
color: var(--app-muted);
font-size: 12px;
font-weight: 750;
}
.segmented button[aria-pressed="true"] {
background: var(--app-surface);
color: var(--app-accent-deep);
box-shadow: var(--shadow);
}
.shell {
display: grid;
grid-template-columns: 150px minmax(0, 1fr) 390px;
min-height: 0;
}
.sidebar {
display: grid;
grid-template-rows: auto 1fr auto;
gap: 14px;
padding: 18px 10px 16px;
border-right: 1px solid var(--app-border);
background: rgba(255, 253, 247, 0.58);
}
.kb-title {
display: grid;
gap: 4px;
padding: 0 4px;
}
.kb-title strong {
font-size: 13px;
font-weight: 800;
}
.kb-title span {
color: var(--app-muted);
font-size: 11px;
font-weight: 650;
}
.nav-list {
display: grid;
align-content: start;
gap: 7px;
}
.nav-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
min-height: 31px;
padding: 0 8px;
border: 1px solid transparent;
border-radius: 9px;
color: var(--app-muted);
font-size: 12px;
font-weight: 680;
}
.nav-item.active {
border-color: color-mix(in srgb, var(--app-accent) 35%, var(--app-border));
background: var(--app-accent-soft);
color: var(--app-accent-deep);
}
.new-kb {
min-height: 34px;
border: 1px solid color-mix(in srgb, var(--app-accent) 22%, var(--app-border));
border-radius: 10px;
background: color-mix(in srgb, var(--app-accent-soft) 45%, transparent);
color: var(--app-accent-deep);
font-size: 12px;
font-weight: 800;
}
.graph-area {
display: grid;
grid-template-rows: auto 1fr;
min-width: 0;
min-height: 0;
padding: 18px;
}
.graph-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 14px;
padding: 10px 12px;
border: 1px solid var(--app-border);
border-radius: 14px;
background: rgba(255, 253, 247, 0.76);
box-shadow: var(--shadow);
}
.graph-toolbar-title {
display: flex;
align-items: center;
gap: 10px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 999px;
background: var(--app-success);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--app-success) 15%, transparent);
}
.graph-toolbar-title strong {
display: block;
font-size: 13px;
font-weight: 800;
}
.graph-toolbar-title span {
display: block;
color: var(--app-muted);
font-size: 11px;
font-weight: 650;
}
.toolbar-buttons {
display: flex;
gap: 8px;
}
.toolbar-buttons button {
min-height: 30px;
padding: 0 10px;
border: 1px solid var(--app-border);
border-radius: 8px;
background: rgba(255, 253, 247, 0.78);
color: var(--app-muted);
font-size: 12px;
font-weight: 750;
}
.graph-canvas {
position: relative;
min-height: 0;
overflow: hidden;
border: 1px solid color-mix(in srgb, var(--app-border) 74%, transparent);
border-radius: 18px;
background:
radial-gradient(circle at 48% 52%, rgba(224, 122, 95, 0.08), transparent 22%),
radial-gradient(circle at 64% 42%, rgba(126, 168, 104, 0.09), transparent 21%),
radial-gradient(circle at 34% 49%, rgba(201, 142, 58, 0.08), transparent 19%),
rgba(255, 253, 247, 0.52);
}
.graph-line {
position: absolute;
height: 1px;
transform-origin: left center;
background: rgba(128, 179, 180, 0.28);
}
.node {
position: absolute;
display: grid;
place-items: center;
width: 10px;
height: 10px;
border-radius: 999px;
background: var(--node-color, var(--app-accent));
box-shadow: 0 0 0 5px rgba(255, 253, 247, 0.78);
}
.node.selected,
.node.preview {
width: 14px;
height: 14px;
box-shadow:
0 0 0 5px rgba(255, 253, 247, 0.84),
0 0 0 10px color-mix(in srgb, var(--app-accent) 17%, transparent);
}
.node-label {
position: absolute;
left: 14px;
top: -7px;
min-width: max-content;
color: rgba(58, 53, 48, 0.65);
font-size: 11px;
font-weight: 700;
}
.community-cloud {
position: absolute;
border: 1px solid rgba(224, 122, 95, 0.18);
border-radius: 999px;
background: rgba(224, 122, 95, 0.035);
}
.community-label {
position: absolute;
left: 42%;
top: 42%;
padding: 4px 8px;
border: 1px solid color-mix(in srgb, var(--app-accent) 24%, var(--app-border));
border-radius: 999px;
background: rgba(255, 253, 247, 0.82);
color: var(--app-accent-deep);
font-size: 11px;
font-weight: 800;
box-shadow: var(--shadow);
}
.drawer-rail {
min-height: 0;
overflow: auto;
padding: 18px 16px 18px 0;
}
.drawer {
display: flex;
flex-direction: column;
gap: 13px;
min-height: calc(100vh - 94px);
padding: 16px;
border: 1px solid color-mix(in srgb, var(--app-border-strong) 70%, transparent);
border-radius: 16px;
background:
linear-gradient(180deg, rgba(255, 253, 247, 0.96), color-mix(in srgb, var(--app-bg) 84%, var(--app-surface))),
var(--paper-grain);
background-size: auto, 180px 180px;
box-shadow: var(--shadow-lg);
}
.drawer-head {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
align-items: start;
}
.kicker-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.kicker {
min-height: 23px;
padding: 3px 8px;
border: 1px solid color-mix(in srgb, var(--app-accent) 27%, var(--app-border));
border-radius: 999px;
background: var(--app-accent-soft);
color: var(--app-accent-deep);
font-size: 11px;
font-weight: 850;
}
.soft-chip {
min-height: 23px;
padding: 3px 8px;
border: 1px solid color-mix(in srgb, var(--app-border) 78%, transparent);
border-radius: 999px;
background: color-mix(in srgb, var(--app-surface) 70%, transparent);
color: var(--app-muted);
font-size: 11px;
font-weight: 750;
}
.drawer-title {
margin: 0;
color: var(--app-fg);
font-size: 20px;
font-weight: 850;
line-height: 1.22;
}
.drawer-desc {
margin: 9px 0 0;
color: color-mix(in srgb, var(--app-fg) 74%, var(--app-muted));
font-size: 13px;
line-height: 1.65;
}
.enter-button {
min-height: 32px;
padding: 0 11px;
border: 1px solid color-mix(in srgb, var(--app-border-strong) 86%, transparent);
border-radius: 9px;
background: color-mix(in srgb, var(--app-surface) 82%, transparent);
color: var(--app-fg);
font-size: 12px;
font-weight: 780;
box-shadow: 0 1px 2px rgba(70, 55, 40, 0.04);
transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease, transform 0.14s ease;
}
.enter-button:hover {
transform: translateY(-1px);
border-color: color-mix(in srgb, var(--app-accent) 34%, var(--app-border));
background: var(--app-accent-soft);
color: var(--app-accent-deep);
}
.fact-strip {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 7px;
}
.fact {
min-width: 0;
padding: 9px 8px;
border: 1px solid color-mix(in srgb, var(--app-border) 76%, transparent);
border-radius: 10px;
background: color-mix(in srgb, var(--app-surface) 72%, transparent);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.55);
}
.fact strong {
display: block;
color: var(--app-accent-deep);
font-size: 17px;
line-height: 1;
}
.fact span {
display: block;
margin-top: 5px;
color: var(--app-muted);
font-size: 11px;
font-weight: 700;
}
.section {
display: grid;
gap: 9px;
padding: 11px;
border: 1px solid color-mix(in srgb, var(--app-border) 70%, transparent);
border-radius: 13px;
background: color-mix(in srgb, var(--app-surface) 50%, transparent);
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.section-title {
display: flex;
align-items: baseline;
gap: 7px;
}
.section-title h3 {
margin: 0;
color: var(--app-fg);
font-size: 12px;
font-weight: 850;
}
.section-title span {
color: var(--app-muted);
font-size: 11px;
font-weight: 700;
}
.text-link {
min-height: 28px;
padding: 0 4px;
border: 0;
background: transparent;
color: var(--app-accent-deep);
font-size: 12px;
font-weight: 820;
}
.action-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.action-button {
display: grid;
gap: 3px;
min-height: 50px;
padding: 8px 10px;
border: 1px solid color-mix(in srgb, var(--app-border-strong) 76%, transparent);
border-radius: 10px;
background: color-mix(in srgb, var(--app-surface) 76%, transparent);
color: var(--app-fg);
text-align: left;
box-shadow: 0 1px 2px rgba(70, 55, 40, 0.04);
transition: transform 0.14s ease, border-color 0.14s ease, background 0.14s ease, box-shadow 0.14s ease;
}
.action-button strong {
font-size: 12px;
font-weight: 820;
line-height: 1.25;
}
.action-button span {
color: var(--app-muted);
font-size: 11px;
font-weight: 650;
}
.action-button:hover {
transform: translateY(-1px);
border-color: color-mix(in srgb, var(--app-accent) 24%, var(--app-border));
background: rgba(255, 253, 247, 0.92);
}
.action-button.recommended {
border-color: color-mix(in srgb, var(--app-accent) 42%, var(--app-border));
background: color-mix(in srgb, var(--app-accent-soft) 72%, var(--app-surface));
color: var(--app-accent-deep);
box-shadow: inset 3px 0 0 var(--app-accent), 0 6px 14px color-mix(in srgb, var(--app-accent) 8%, transparent);
}
.action-button.recommended span {
color: color-mix(in srgb, var(--app-accent-deep) 72%, var(--app-muted));
}
.core-list {
display: grid;
gap: 7px;
margin: 0;
padding: 0;
list-style: none;
}
.core-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
align-items: center;
gap: 8px;
width: 100%;
min-height: 39px;
padding: 8px 9px;
border: 1px solid color-mix(in srgb, var(--app-border) 70%, transparent);
border-radius: 10px;
background: color-mix(in srgb, var(--app-bg) 45%, transparent);
color: var(--app-fg);
text-align: left;
transition: transform 0.14s ease, border-color 0.14s ease, background 0.14s ease, box-shadow 0.14s ease;
}
.core-row:hover,
.core-row:focus-visible {
transform: translateX(-2px);
border-color: color-mix(in srgb, var(--app-accent) 42%, var(--app-border));
background:
linear-gradient(90deg, var(--app-accent-soft), rgba(255, 253, 247, 0.92) 88%);
box-shadow: inset 3px 0 0 var(--app-accent), 0 8px 18px color-mix(in srgb, var(--app-accent) 10%, transparent);
outline: 0;
}
.core-name {
overflow: hidden;
font-size: 12px;
font-weight: 800;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
}
.role-chip {
min-width: 42px;
padding: 3px 8px;
border: 1px solid color-mix(in srgb, var(--app-border) 78%, transparent);
border-radius: 999px;
background: rgba(255, 253, 247, 0.72);
color: var(--app-muted);
font-size: 11px;
font-weight: 780;
text-align: center;
}
.row-affordance {
opacity: 0;
color: var(--app-accent-deep);
font-size: 13px;
font-weight: 900;
transition: opacity 0.14s ease;
}
.core-row:hover .row-affordance,
.core-row:focus-visible .row-affordance {
opacity: 1;
}
.dialogue {
display: grid;
gap: 8px;
margin-top: auto;
}
.textarea {
width: 100%;
min-height: 78px;
resize: none;
padding: 10px 11px;
border: 1px solid color-mix(in srgb, var(--app-border) 80%, transparent);
border-radius: 11px;
background: color-mix(in srgb, var(--app-raised) 48%, var(--app-surface));
color: var(--app-fg);
font-size: 13px;
line-height: 1.55;
outline: 0;
transition: border-color 0.14s ease, box-shadow 0.14s ease, background 0.14s ease;
}
.textarea:focus {
border-color: color-mix(in srgb, var(--app-accent) 58%, var(--app-border));
background: var(--app-surface);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-accent) 13%, transparent);
}
.dialogue-footer {
display: grid;
gap: 8px;
}
.hint {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
color: var(--app-muted);
font-size: 11px;
font-weight: 700;
}
.hint-mark {
width: 6px;
height: 6px;
flex: 0 0 auto;
border-radius: 999px;
background: var(--app-success);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-success) 14%, transparent);
}
.dialogue-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.secondary-button,
.send-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
min-height: 34px;
padding: 0 12px;
border-radius: 10px;
font-size: 12px;
font-weight: 820;
transition: transform 0.14s ease, border-color 0.14s ease, background 0.14s ease, box-shadow 0.14s ease;
}
.secondary-button svg,
.send-button svg {
width: 14px;
height: 14px;
flex: 0 0 auto;
}
.secondary-button {
border: 1px solid color-mix(in srgb, var(--app-border-strong) 82%, transparent);
background: color-mix(in srgb, var(--app-surface) 76%, transparent);
color: var(--app-fg);
}
.send-button {
border: 1px solid color-mix(in srgb, var(--app-accent) 64%, var(--app-border));
background:
linear-gradient(180deg, color-mix(in srgb, var(--app-accent) 88%, #fff4ef), var(--app-accent));
color: #fffdf7;
box-shadow: 0 8px 18px color-mix(in srgb, var(--app-accent) 22%, transparent);
}
.secondary-button:hover,
.send-button:hover {
transform: translateY(-1px);
}
.secondary-button:hover {
border-color: color-mix(in srgb, var(--app-accent) 24%, var(--app-border));
background: rgba(255, 253, 247, 0.92);
}
.send-button:hover {
box-shadow: 0 11px 22px color-mix(in srgb, var(--app-accent) 25%, transparent);
}
.send-button[disabled] {
border-color: color-mix(in srgb, var(--app-accent) 34%, var(--app-border));
background:
linear-gradient(180deg, color-mix(in srgb, var(--app-accent-soft) 84%, var(--app-surface)), color-mix(in srgb, var(--app-accent-soft) 72%, var(--app-raised)));
color: color-mix(in srgb, var(--app-accent-deep) 52%, var(--app-faint));
box-shadow: 0 5px 12px color-mix(in srgb, var(--app-accent) 9%, transparent);
}
@media (max-width: 1000px) {
body {
overflow: auto;
}
.prototype {
height: auto;
min-height: 100vh;
}
.shell {
grid-template-columns: 1fr;
}
.sidebar {
display: none;
}
.drawer-rail {
padding: 0 18px 18px;
}
.drawer {
min-height: auto;
}
}
@media (max-width: 560px) {
.review-bar {
align-items: flex-start;
flex-direction: column;
padding: 12px;
}
.review-controls,
.segmented {
width: 100%;
}
.segmented button {
flex: 1;
}
.graph-area {
padding: 12px;
}
.drawer-head,
.fact-strip,
.action-grid {
grid-template-columns: minmax(0, 1fr);
}
.enter-button {
justify-self: start;
}
.dialogue-actions {
width: 100%;
grid-template-columns: minmax(0, 1fr);
}
.dialogue-actions button {
width: 100%;
}
}
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel">
const community = {
kind: "community",
kicker: "社区",
title: "知识库构建",
desc: "这组页面围绕同一主题聚在一起。你可以先看结构,也可以直接让 agent 基于这一组页面继续工作。",
canEnter: true,
status: "结构清晰",
recommended: "summary",
facts: [
["6", "页"],
["11", "链接"],
["5", "核心"],
["0", "孤立"],
],
nodes: [
["llm-wiki-skill", "核心"],
["Andrej Karpathy", "相关"],
["知识库构建", "主题"],
["分层记忆", "相关"],
["RAG vs llm-wiki", "相关"],
],
};
const ungrouped = {
kind: "community",
kicker: "社区",
title: "未分组",
desc: "这些页面暂未形成明确社区。你可以让 agent 探索它们之间是否存在潜在关系。",
canEnter: false,
status: "暂未成组",
recommended: "explore",
facts: [
["4", "页"],
["1", "链接"],
["1", "核心"],
["3", "孤立"],
],
nodes: [
["个人公司:项目立项", "已选"],
["提示词炼金术", "已选"],
["一人公司工具链", "已选"],
["OpenClaw 调研摘要", "已选"],
],
};
const actionCopy = [
["summary", "总结这一簇", "先形成概览"],
["gap", "找知识缺口", "发现薄弱处"],
["topic", "生成主题页", "沉淀为页面"],
["explore", "探索潜在关系", "找隐藏连接"],
];
function App() {
const [mode, setMode] = React.useState("community");
const [expanded, setExpanded] = React.useState(false);
const [hoverNode, setHoverNode] = React.useState("Andrej Karpathy");
const [note, setNote] = React.useState("");
const data = mode === "community" ? community : ungrouped;
const visibleNodes = expanded ? data.nodes : data.nodes.slice(0, 3);
React.useEffect(() => {
setExpanded(false);
setHoverNode(data.nodes[1]?.[0] || data.nodes[0]?.[0] || "");
setNote("");
}, [mode]);
return (
<main className="prototype" data-screen-label="PR82 抽屉重设计">
<header className="review-bar">
<div className="review-title">
<strong>PR82 抽屉重设计</strong>
<span>只加回核心节点展开鼠标反馈对话轻提示</span>
</div>
<div className="review-controls">
<div className="segmented" aria-label="场景切换">
<button type="button" aria-pressed={mode === "community"} onClick={() => setMode("community")}>普通社区</button>
<button type="button" aria-pressed={mode === "ungrouped"} onClick={() => setMode("ungrouped")}>未分组</button>
</div>
</div>
</header>
<section className="shell">
<aside className="sidebar" aria-label="笔记本">
<div className="kb-title">
<strong>kiro 小红书爆款文章</strong>
<span>外部 · 可用</span>
</div>
<nav className="nav-list">
<div className="nav-item">AI工具系列 <span>外部</span></div>
<div className="nav-item">AI学习知识库 <span>外部</span></div>
<div className="nav-item active">kiro 小红书爆款文章 <span>外部</span></div>
<div className="nav-item">测试 <span>6/28</span></div>
<div className="nav-item">当前知识库下的文章 <span>6/15</span></div>
</nav>
<button className="new-kb" type="button">新建知识库</button>
</aside>
<section className="graph-area" aria-label="图谱">
<div className="graph-toolbar">
<div className="graph-toolbar-title">
<span className="status-dot"></span>
<div>
<strong>kiro 小红书爆款文章</strong>
<span>图谱就绪 · 32 节点 · 64 关联</span>
</div>
</div>
<div className="toolbar-buttons">
<button type="button">筛选</button>
<button type="button">图例</button>
<button type="button">重布局</button>
</div>
</div>
<GraphCanvas hoverNode={hoverNode} mode={mode} />
</section>
<aside className="drawer-rail" aria-label="右侧抽屉">
<Drawer
data={data}
expanded={expanded}
note={note}
visibleNodes={visibleNodes}
setExpanded={setExpanded}
setHoverNode={setHoverNode}
setNote={setNote}
/>
</aside>
</section>
</main>
);
}
function Drawer({ data, expanded, note, visibleNodes, setExpanded, setHoverNode, setNote }) {
return (
<article className="drawer" data-screen-label="统一社区抽屉">
<div className="drawer-head">
<div>
<div className="kicker-row">
<span className="kicker">{data.kicker}</span>
<span className="soft-chip">{data.status}</span>
</div>
<h2 className="drawer-title">{data.title}</h2>
<p className="drawer-desc">{data.desc}</p>
</div>
{data.canEnter ? <button type="button" className="enter-button">进入社区</button> : null}
</div>
<div className="fact-strip" aria-label="社区统计">
{data.facts.map(([value, label]) => (
<div className="fact" key={label}>
<strong>{value}</strong>
<span>{label}</span>
</div>
))}
</div>
<section className="section">
<div className="section-header">
<div className="section-title">
<h3>下一步</h3>
<span> agent 继续工作</span>
</div>
</div>
<div className="action-grid">
{actionCopy.map(([id, title, detail]) => (
<button type="button" className={"action-button" + (data.recommended === id ? " recommended" : "")} key={id}>
<strong>{title}</strong>
<span>{data.recommended === id ? "推荐" : detail}</span>
</button>
))}
</div>
</section>
<section className="section">
<div className="section-header">
<div className="section-title">
<h3>核心节点</h3>
<span>{data.nodes.length} </span>
</div>
{data.nodes.length > 3 ? (
<button type="button" className="text-link" onClick={() => setExpanded(!expanded)}>
{expanded ? "收起" : "查看全部"}
</button>
) : null}
</div>
<ul className="core-list">
{visibleNodes.map(([name, role]) => (
<li key={name}>
<button
type="button"
className="core-row"
onMouseEnter={() => setHoverNode(name)}
onFocus={() => setHoverNode(name)}
>
<span className="core-name">{name}</span>
<span className="role-chip">{role}</span>
<span className="row-affordance" aria-hidden="true"></span>
</button>
</li>
))}
</ul>
</section>
<section className="dialogue" aria-label="对话区">
<textarea
className="textarea"
value={note}
onChange={(event) => setNote(event.target.value)}
placeholder="补充说明(可选)"
></textarea>
<div className="dialogue-footer">
<div className="hint">
<span className="hint-mark"></span>
<span>{data.kind === "community" ? "当前社区会带入对话" : "当前选区会带入对话"}</span>
</div>
<div className="dialogue-actions">
<button type="button" className="send-button" disabled={note.trim().length === 0}>
<SendIcon />
<span>发送</span>
</button>
<button type="button" className="secondary-button">
<NewChatIcon />
<span>新对话</span>
</button>
</div>
</div>
</section>
</article>
);
}
function SendIcon() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="m22 2-7 20-4-9-9-4Z"></path>
<path d="M22 2 11 13"></path>
</svg>
);
}
function NewChatIcon() {
return (
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 8v8"></path>
<path d="M8 12h8"></path>
<path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4Z"></path>
</svg>
);
}
function GraphCanvas({ hoverNode, mode }) {
const nodes = [
["llm-wiki-skill", 48, 58, "#e07a5f"],
["Andrej Karpathy", 55, 50, "#e07a5f"],
["知识库构建", 44, 49, "#e07a5f"],
["分层记忆", 38, 61, "#c98e3a"],
["RAG vs llm-wiki", 51, 66, "#c98e3a"],
["个人公司", 71, 46, "#7ea868"],
["提示词炼金术", 76, 39, "#7ea868"],
["OpenClaw", 31, 45, "#d8b15d"],
["dontbesilent", 66, 29, "#b69ad7"],
];
const lines = [
[43, 49, 13, -5, 18],
[50, 58, -12, 7, -24],
[55, 50, 16, -3, -10],
[49, 58, 23, -12, -17],
[39, 61, 16, 5, 12],
[31, 45, 18, 12, 28],
[66, 29, 11, 10, 35],
];
return (
<div className="graph-canvas">
<div className="community-cloud" style={{ left: "33%", top: "38%", width: "29%", height: "36%" }}></div>
<div className="community-label">{mode === "community" ? "知识库构建" : "未分组"}</div>
{lines.map(([left, top, width, y, rotate], index) => (
<span
className="graph-line"
key={index}
style={{
left: `${left}%`,
top: `${top}%`,
width: `${Math.abs(width) * 1.8}%`,
transform: `rotate(${rotate + y}deg)`,
}}
></span>
))}
{nodes.map(([name, left, top, color]) => (
<span
className={"node " + (name === hoverNode ? "preview" : "") + (name === "Andrej Karpathy" && mode === "community" ? " selected" : "")}
key={name}
style={{ left: `${left}%`, top: `${top}%`, "--node-color": color }}
>
<span className="node-label">{name}</span>
</span>
))}
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
</script>
</body>
</html>