|
7 | 7 | * 视频类型档案卡(选定窗口后出现)+ 融合完成直达卡片(A4)。 |
8 | 8 | * @ai-context: 纯展示装配(无自有事件监听——活动态由 LiveActivityPanel 自监听), |
9 | 9 | * 状态全部由父组件注入。 |
| 10 | + * @ai-context: 批 3(规格 §6.2「统一 wrapper,消灭 640/全宽两档跳动」):非采集态内容 |
| 11 | + * 的宽度上限**只由 wrapper 一处**决定——此前三处分支各写一份 640,而结果态 |
| 12 | + * 预览完全没有上限 ⇒ 状态切换时在 640 与全宽之间跳。 |
10 | 13 | */ |
11 | 14 | import LiveActivityPanel from "./LiveActivityPanel"; |
12 | 15 | import ProfileDetector from "./ProfileDetector"; |
| 16 | +import { columnSpec } from "../shell/columnRegistry"; |
13 | 17 | import type { Note, ProfileKind, WindowInfo } from "../types"; |
14 | 18 |
|
15 | 19 | const btn: React.CSSProperties = { padding: "6px 12px", cursor: "pointer", fontSize: 13 }; |
16 | 20 | const panel: React.CSSProperties = { border: "1px solid #e5e7eb", borderRadius: 8, padding: 12 }; |
17 | 21 |
|
| 22 | +/** |
| 23 | + * 非采集态内容区的**唯一**宽度上限(规格 §6.2:右面板是 flex 列,「统一 wrapper」)。 |
| 24 | + * Why 不取 `classroom-right`:注册表里它是 `default: 0` 的 flex 列(宽由容器决定,连 `max` |
| 25 | + * 也是 0)⇒ 取不到内容宽。这里改为引用注册表里**语义就是「居中阅读舒适宽」的那一行** |
| 26 | + * (`settings-main`,规格 §6.2 写「居中 860」)⇒ 这个数字全仓只有一份,改注册表即两边同步, |
| 27 | + * 本文件不复制字面量(重复登记的风险由此消除)。 |
| 28 | + * 边界:只管非采集态内容;采集态由 `LiveActivityPanel` 独占整栏(宿主 `flex: 1` 未变)。 |
| 29 | + */ |
| 30 | +const PANE_BODY_MAX = columnSpec("settings-main").default; |
| 31 | + |
18 | 32 | interface Props { |
19 | 33 | /** 活动态(采集中/停止过渡/融合中——右栏由活动面板独占) */ |
20 | 34 | liveActive: boolean; |
@@ -55,80 +69,83 @@ export default function ClassroomRightPane({ |
55 | 69 | /> |
56 | 70 | ) : ( |
57 | 71 | <div style={{ flex: 1, minHeight: 0, overflowY: "auto" }}> |
58 | | - {/* A4:融合完成直达卡片(停止后右侧顶部;一键跳会话页定位) */} |
59 | | - {fusedSessionId && ( |
60 | | - <div style={{ padding: "12px 16px 0", maxWidth: 640 }}> |
61 | | - <div style={{ display: "flex", alignItems: "center", gap: 8, border: "1px solid #99f6e4", background: "#f0fdfa", borderRadius: 8, padding: "8px 12px" }}> |
62 | | - <span style={{ fontSize: 12, color: "#0f766e" }}>✅ 融合完成</span> |
63 | | - <button |
64 | | - onClick={() => onOpenSessions?.(fusedSessionId)} |
65 | | - style={{ ...btn, marginLeft: "auto", background: "#0d9488", color: "#fff", border: "none", borderRadius: 6 }} |
66 | | - > |
67 | | - 查看时间轴 → |
68 | | - </button> |
69 | | - <button |
70 | | - onClick={onDismissFused} |
71 | | - style={{ ...btn, border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" }} |
72 | | - title="关闭提示" |
73 | | - > |
74 | | - ✕ |
75 | | - </button> |
| 72 | + {/* 批 3(规格 §6.2):唯一的内容宽决定点(值取自注册表,见上) */} |
| 73 | + <div style={{ maxWidth: PANE_BODY_MAX, margin: "0 auto" }}> |
| 74 | + {/* A4:融合完成直达卡片(停止后右侧顶部;一键跳会话页定位) */} |
| 75 | + {fusedSessionId && ( |
| 76 | + <div style={{ padding: "12px 16px 0" }}> |
| 77 | + <div style={{ display: "flex", alignItems: "center", gap: 8, border: "1px solid #99f6e4", background: "#f0fdfa", borderRadius: 8, padding: "8px 12px" }}> |
| 78 | + <span style={{ fontSize: 12, color: "#0f766e" }}>✅ 融合完成</span> |
| 79 | + <button |
| 80 | + onClick={() => onOpenSessions?.(fusedSessionId)} |
| 81 | + style={{ ...btn, marginLeft: "auto", background: "#0d9488", color: "#fff", border: "none", borderRadius: 6 }} |
| 82 | + > |
| 83 | + 查看时间轴 → |
| 84 | + </button> |
| 85 | + <button |
| 86 | + onClick={onDismissFused} |
| 87 | + style={{ ...btn, border: "1px solid #e5e7eb", borderRadius: 6, background: "#fff" }} |
| 88 | + title="关闭提示" |
| 89 | + > |
| 90 | + ✕ |
| 91 | + </button> |
| 92 | + </div> |
76 | 93 | </div> |
| 94 | + )} |
| 95 | + {/* 视频类型档案(v0.5.0 M1:REQ-043 混合检测——自动候选 + 用户确认 + 记忆偏好; |
| 96 | + 右侧配置区:选定窗口后出现,未选窗口自动隐藏) */} |
| 97 | + <div style={{ padding: "12px 16px 0" }}> |
| 98 | + <ProfileDetector |
| 99 | + windowTitle={selectedWindow?.title ?? null} |
| 100 | + onProfileChange={onProfileChange} |
| 101 | + /> |
77 | 102 | </div> |
78 | | - )} |
79 | | - {/* 视频类型档案(v0.5.0 M1:REQ-043 混合检测——自动候选 + 用户确认 + 记忆偏好; |
80 | | - 右侧配置区:选定窗口后出现,未选窗口自动隐藏) */} |
81 | | - <div style={{ padding: "12px 16px 0", maxWidth: 640 }}> |
82 | | - <ProfileDetector |
83 | | - windowTitle={selectedWindow?.title ?? null} |
84 | | - onProfileChange={onProfileChange} |
85 | | - /> |
86 | | - </div> |
87 | | - {lastNote ? ( |
88 | | - /* 结果态:最近生成的笔记预览 */ |
89 | | - <div style={{ padding: 16 }}> |
90 | | - <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}> |
91 | | - <h2 style={{ margin: 0, fontSize: 16 }}>{lastNote.title}</h2> |
92 | | - <span style={{ fontSize: 11, color: "#6b7280" }}> |
93 | | - {/* REQ-277:元信息不带裸 # 数字(id 仅内部引用) */} |
94 | | - {lastNote.source} · 更新于 {new Date(lastNote.updated_at * 1000).toLocaleString()} |
95 | | - </span> |
| 103 | + {lastNote ? ( |
| 104 | + /* 结果态:最近生成的笔记预览 */ |
| 105 | + <div style={{ padding: 16 }}> |
| 106 | + <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}> |
| 107 | + <h2 style={{ margin: 0, fontSize: 16 }}>{lastNote.title}</h2> |
| 108 | + <span style={{ fontSize: 11, color: "#6b7280" }}> |
| 109 | + {/* REQ-277:元信息不带裸 # 数字(id 仅内部引用) */} |
| 110 | + {lastNote.source} · 更新于 {new Date(lastNote.updated_at * 1000).toLocaleString()} |
| 111 | + </span> |
| 112 | + </div> |
| 113 | + <pre |
| 114 | + style={{ |
| 115 | + background: "#f9fafb", |
| 116 | + border: "1px solid #e5e7eb", |
| 117 | + borderRadius: 8, |
| 118 | + padding: 14, |
| 119 | + whiteSpace: "pre-wrap", |
| 120 | + fontSize: 13, |
| 121 | + lineHeight: 1.7, |
| 122 | + }} |
| 123 | + > |
| 124 | + {lastNote.content} |
| 125 | + </pre> |
| 126 | + <p style={{ fontSize: 12, color: "#6b7280" }}>已保存至笔记,可在「笔记」页继续编辑与检索。</p> |
96 | 127 | </div> |
97 | | - <pre |
98 | | - style={{ |
99 | | - background: "#f9fafb", |
100 | | - border: "1px solid #e5e7eb", |
101 | | - borderRadius: 8, |
102 | | - padding: 14, |
103 | | - whiteSpace: "pre-wrap", |
104 | | - fontSize: 13, |
105 | | - lineHeight: 1.7, |
106 | | - }} |
107 | | - > |
108 | | - {lastNote.content} |
109 | | - </pre> |
110 | | - <p style={{ fontSize: 12, color: "#6b7280" }}>已保存至笔记,可在「笔记」页继续编辑与检索。</p> |
111 | | - </div> |
112 | | - ) : ( |
113 | | - /* 空态:当前配置说明书(参考原项目 IdleGuidePanel) */ |
114 | | - <div style={{ padding: "16px 24px 24px", maxWidth: 640 }}> |
115 | | - <h2 style={{ fontSize: 18 }}>使用说明</h2> |
116 | | - <ol style={{ fontSize: 13, lineHeight: 2, color: "#374151" }}> |
117 | | - <li><strong>选择目标窗口/进程</strong>:自动推荐疑似网课/视频窗口(B站/播放器/浏览器),也可展开全部手动选择——将作为笔记标题与实时捕获目标;无法采集的窗口(最小化/悬浮层)与站点首页(如 B站首页)已自动过滤</li> |
118 | | - <li><strong>实时捕获</strong>:系统声音 + 屏幕字幕 + 流式转写(Zipformer)边看边记,右侧实时显示转写与画面图片,停止后到「会话」页查看时间轴并可一键转笔记</li> |
119 | | - <li><strong>添加学习素材</strong>:音频文件(WAV,本地 SenseVoice 转写)与图片(本地 PP-OCRv6 识别)</li> |
120 | | - <li><strong>一键提取</strong>:转写 + OCR → 本地拼接为 Markdown 笔记 → 自动保存</li> |
121 | | - </ol> |
122 | | - <div style={{ ...panel, marginTop: 16, fontSize: 12, color: "#6b7280", lineHeight: 1.9 }}> |
123 | | - <div><strong>当前配置</strong></div> |
124 | | - <div>目标窗口:{selectedWindow ? `${selectedWindow.title}(${selectedWindow.processName || "未知进程"})` : "未选择(实时捕获将抓全屏)"}</div> |
125 | | - <div>流式转写:sherpa-onnx Zipformer(实时字幕,需模型就绪)</div> |
126 | | - <div>转写引擎:sherpa-onnx SenseVoice(本地,已就绪)</div> |
127 | | - <div>OCR 引擎:oar-ocr PP-OCRv6(本地,首次使用自动下载模型)</div> |
128 | | - <div>数据主权:全部本地处理,内容不出本机</div> |
| 128 | + ) : ( |
| 129 | + /* 空态:当前配置说明书(参考原项目 IdleGuidePanel) */ |
| 130 | + <div style={{ padding: "16px 24px 24px" }}> |
| 131 | + <h2 style={{ fontSize: 18 }}>使用说明</h2> |
| 132 | + <ol style={{ fontSize: 13, lineHeight: 2, color: "#374151" }}> |
| 133 | + <li><strong>选择目标窗口/进程</strong>:自动推荐疑似网课/视频窗口(B站/播放器/浏览器),也可展开全部手动选择——将作为笔记标题与实时捕获目标;无法采集的窗口(最小化/悬浮层)与站点首页(如 B站首页)已自动过滤</li> |
| 134 | + <li><strong>实时捕获</strong>:系统声音 + 屏幕字幕 + 流式转写(Zipformer)边看边记,右侧实时显示转写与画面图片,停止后到「会话」页查看时间轴并可一键转笔记</li> |
| 135 | + <li><strong>添加学习素材</strong>:音频文件(WAV,本地 SenseVoice 转写)与图片(本地 PP-OCRv6 识别)</li> |
| 136 | + <li><strong>一键提取</strong>:转写 + OCR → 本地拼接为 Markdown 笔记 → 自动保存</li> |
| 137 | + </ol> |
| 138 | + <div style={{ ...panel, marginTop: 16, fontSize: 12, color: "#6b7280", lineHeight: 1.9 }}> |
| 139 | + <div><strong>当前配置</strong></div> |
| 140 | + <div>目标窗口:{selectedWindow ? `${selectedWindow.title}(${selectedWindow.processName || "未知进程"})` : "未选择(实时捕获将抓全屏)"}</div> |
| 141 | + <div>流式转写:sherpa-onnx Zipformer(实时字幕,需模型就绪)</div> |
| 142 | + <div>转写引擎:sherpa-onnx SenseVoice(本地,已就绪)</div> |
| 143 | + <div>OCR 引擎:oar-ocr PP-OCRv6(本地,首次使用自动下载模型)</div> |
| 144 | + <div>数据主权:全部本地处理,内容不出本机</div> |
| 145 | + </div> |
129 | 146 | </div> |
130 | | - </div> |
131 | | - )} |
| 147 | + )} |
| 148 | + </div> |
132 | 149 | </div> |
133 | 150 | )} |
134 | 151 | </div> |
|
0 commit comments