@blade-hq/agent-react 2610.0.0-beta.70 → 2610.0.0-beta.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +160 -5
  2. package/dist/{chunk-MAAMCOAU.js → chunk-IA6J2NTB.js} +4 -3
  3. package/dist/components/AgentLoopBlock.d.ts +2 -1
  4. package/dist/components/AskUserQuestionBlock.d.ts +3 -1
  5. package/dist/components/AssistantTurnBlock.d.ts +8 -4
  6. package/dist/components/ChatInput.d.ts +7 -1
  7. package/dist/components/ChatSurface.d.ts +105 -1
  8. package/dist/components/ChatView.d.ts +1 -1
  9. package/dist/components/MessageList.d.ts +13 -4
  10. package/dist/components/PlanUpdateBlock.d.ts +2 -1
  11. package/dist/components/PostChatFollowupBlock.d.ts +5 -1
  12. package/dist/components/ToolCallBlock.d.ts +6 -2
  13. package/dist/components/TurnNavRail.d.ts +23 -0
  14. package/dist/components/UserMessageBubble.d.ts +6 -2
  15. package/dist/embed/blade-chat-element.d.ts +4 -3
  16. package/dist/embed/entry.d.ts +2 -0
  17. package/dist/{highlighted-body-B3W2YXNL-IASCZDV2.js → highlighted-body-B3W2YXNL-YF4GVJGP.js} +2 -2
  18. package/dist/hooks/use-turn-navigation.d.ts +117 -0
  19. package/dist/index.d.ts +6 -1
  20. package/dist/index.js +1911 -1176
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/utils.d.ts +15 -2
  23. package/dist/{mermaid-3ZIDBTTL-BPVVWTVV.js → mermaid-3ZIDBTTL-QL4YGD6I.js} +2 -2
  24. package/dist/style.css +488 -494
  25. package/dist/style.full.css +499 -498
  26. package/package.json +2 -2
  27. package/public-api.md +329 -4
  28. /package/dist/{chunk-MAAMCOAU.js.map → chunk-IA6J2NTB.js.map} +0 -0
  29. /package/dist/{highlighted-body-B3W2YXNL-IASCZDV2.js.map → highlighted-body-B3W2YXNL-YF4GVJGP.js.map} +0 -0
  30. /package/dist/{mermaid-3ZIDBTTL-BPVVWTVV.js.map → mermaid-3ZIDBTTL-QL4YGD6I.js.map} +0 -0
package/dist/style.css CHANGED
@@ -1,11 +1,107 @@
1
1
  /*
2
2
  * @blade-hq/agent-react 自包含样式。
3
3
  *
4
- * 组件同时携带 Tailwind class 与 blade-chat-* 前缀类:宿主项目装了 Tailwind
5
- * Tailwind 提供完整视觉;没装时,本文件为核心布局与配色提供等效的普通 CSS,
6
- * 保证聊天界面开箱可用。所有颜色经由 CSS 变量,宿主可整体换肤。
4
+ * 这份文件只放 Tailwind 工具类表达不了的东西,其余视觉一律由组件上的 Tailwind
5
+ * class 定义一次(R1:一个视觉效果只在一处定义):
6
+ *
7
+ * - CSS 变量(主题、圆角、gutter)与 `:host` 尺寸默认值;
8
+ * - 容器查询(`@container`):按「容器宽度」而不是视口伸缩的内边距与断点;
9
+ * - Markdown 排版:正文是 streamdown 生成的 HTML,没有类名可挂,只能在这里写;
10
+ * - 无 Tailwind 预检时组件仍需要的少量 rest(按钮游标、textarea 去边框);
11
+ * - 跨元素的形状约束,如 `.blade-chat-messages-content > div` 的轮次间距。
12
+ *
13
+ * 组件上的 Tailwind class 不再需要在这里重复一份。视觉规则的层序见下面
14
+ * `@layer` 声明:兜底在 components 层,Tailwind 工具类在 utilities 层,
15
+ * 接入方无层的普通 CSS 天然最高优先——所以 `.blade-chat-user-bubble { background: red }`
16
+ * 一定生效,不需要 `!important`。
17
+ */
18
+
19
+ /* ------------------------------------------------------------ 语义锚点清单
20
+ *
21
+ * 公开契约:每个可见区块的根元素固定带一个 blade-chat-* 类,接入方用普通 CSS
22
+ * 选它即可覆盖 SDK 默认样式(README「样式定制」第 4 层),不需要 !important,
23
+ * 也不需要跟着 SDK 的构建顺序走。
24
+ *
25
+ * 锚点只承担「可被选中」职责,不承载视觉规则。下面的兜底样式里出现的锚点,
26
+ * 只是恰好需要兜底的那几个;清单本身才是契约的一部分。
27
+ *
28
+ * scripts/gen-embed-styles.mjs 会校验这份清单的两件事:源码里出现的每个锚点都
29
+ * 必须在这里登记;清单里的每个锚点都必须出现在三份 CSS 产物中。新增可见区块时
30
+ * 先在这里登记锚点,再让元素带上它。
31
+ *
32
+ * @blade-chat-anchors
33
+ * blade-chat
34
+ * blade-chat-advanced
35
+ * blade-chat-agent-loop
36
+ * blade-chat-ask-card
37
+ * blade-chat-ask-option
38
+ * blade-chat-assistant-text
39
+ * blade-chat-assistant-turn
40
+ * blade-chat-banner
41
+ * blade-chat-codeblock
42
+ * blade-chat-codeblock-body
43
+ * blade-chat-codeblock-header
44
+ * blade-chat-context-card
45
+ * blade-chat-context-chevron
46
+ * blade-chat-context-copy
47
+ * blade-chat-context-detail
48
+ * blade-chat-context-group-items
49
+ * blade-chat-context-icon
50
+ * blade-chat-context-status
51
+ * blade-chat-context-summary
52
+ * blade-chat-context-title
53
+ * blade-chat-empty
54
+ * blade-chat-error-bar
55
+ * blade-chat-error-block
56
+ * blade-chat-error-row
57
+ * blade-chat-file-card
58
+ * blade-chat-file-card-icon
59
+ * blade-chat-file-card-name
60
+ * blade-chat-followup
61
+ * blade-chat-followup-action
62
+ * blade-chat-host
63
+ * blade-chat-input
64
+ * blade-chat-input-inner
65
+ * blade-chat-input-toolbar
66
+ * blade-chat-login
67
+ * blade-chat-markdown
68
+ * blade-chat-mcp-app
69
+ * blade-chat-memory-refs
70
+ * blade-chat-messages
71
+ * blade-chat-messages-content
72
+ * blade-chat-messages-scroll
73
+ * blade-chat-plan
74
+ * blade-chat-plan-block
75
+ * blade-chat-prose
76
+ * blade-chat-queue
77
+ * blade-chat-queue-item
78
+ * blade-chat-queue-notice
79
+ * blade-chat-render-error
80
+ * blade-chat-replay-bar
81
+ * blade-chat-scroll-bottom
82
+ * blade-chat-scroll-bottom-label
83
+ * blade-chat-textarea
84
+ * blade-chat-thinking
85
+ * blade-chat-tool-row
86
+ * blade-chat-user-bubble
87
+ * blade-chat-user-col
88
+ * blade-chat-user-row
7
89
  */
8
90
 
91
+ /* 层序先定死:兜底排在 Tailwind 工具类之前,换肤和覆盖才有稳定的先后。
92
+ * 三份产物都从这一行开始,Tailwind 自己那份声明用的是同一个顺序。
93
+ *
94
+ * properties 是 Tailwind 自己的 --tw-* 初始值层,必须排在最前:它的 `* { --tw-*: … }`
95
+ * 一旦排在 utilities 之后,就会把工具类设的 translate / ring / shadow 变量初始化掉。
96
+ *
97
+ * 最后一层 blade-chat-overrides 是给「必须压过组件自己 Tailwind 类」的规则用的:窄容器里
98
+ * 放宽用户消息宽度、iOS 聚焦不缩放、代码块与 Markdown 的排版——这些规则一旦
99
+ * 掉进 components 层就会被同元素的 utilities 盖掉,等于没写。接入方的普通
100
+ * CSS 不分层,仍然赢过这里所有层。 */
101
+ @layer properties, theme, base, components, utilities, blade-chat-overrides;
102
+
103
+ /* 变量不分层:它是主题契约,接入方在页面或祖先元素上重设同名变量即可换肤,
104
+ * 分层反而让"谁覆盖谁"取决于产物拼接顺序。 */
9
105
  :root,
10
106
  :host {
11
107
  --background: 0 0% 100%;
@@ -54,588 +150,486 @@
54
150
  --user-msg-border: 38 30% 19%;
55
151
  }
56
152
 
57
- /* ---------------------------------------------------------------- 布局骨架 */
58
-
59
- /* <blade-chat> 宿主默认块级 + 默认高度;页面对元素写的任何尺寸规则都会覆盖
60
- * :host 默认值。host 容器把高度传导给内部 flex 链,消息区撑满、输入框钉底。 */
61
- :host {
62
- display: block;
63
- height: 560px;
64
- }
153
+ @layer components {
154
+ /* -------------------------------------------------------------- 布局骨架 */
65
155
 
66
- .blade-chat-host {
67
- height: 100%;
68
- display: flex;
69
- flex-direction: column;
70
- }
156
+ /* <blade-chat> 宿主默认块级 + 默认高度;页面对元素写的任何尺寸规则都会覆盖
157
+ * :host 默认值。host 容器把高度传导给内部 flex 链,消息区撑满、输入框钉底。 */
158
+ :host {
159
+ display: block;
160
+ height: 560px;
161
+ }
71
162
 
72
- /* 聊天根容器。SDK 常被嵌进只有 300-400px 宽的侧栏,而宿主页面视口是全宽的,
73
- * 所以内边距一律按「容器宽度」伸缩,不能用视口断点——那会在窄容器里反向命中最大值。
74
- * container-type 建在这里(而不是消息区),是为了让消息区和输入区共享同一个 cqi
75
- * 基准,两者左右边缘才对得齐。代价是内部内容不再向外传递固有宽度,所以宿主需要给
76
- * 容器一个确定宽度(flex/grid 拉伸或显式宽度都可以)。 */
77
- .blade-chat {
78
- /* 具名容器:宿主或以后的子组件万一在中间又建了个匿名 query container,
79
- * 下面的 @container 查询不会悄悄改成以那个为基准。 */
80
- container: blade-chat / inline-size;
81
- display: flex;
82
- flex-direction: column;
83
- flex: 1 1 0%;
84
- min-height: 0;
85
- min-width: 0;
86
- overflow: hidden;
87
- --blade-chat-gutter: 1rem;
88
- --blade-chat-turn-gap: 1.5rem;
89
- background: hsl(var(--background));
90
- color: hsl(var(--foreground));
91
- font-family:
92
- system-ui,
93
- -apple-system,
94
- "Segoe UI",
95
- Roboto,
96
- "PingFang SC",
97
- "Microsoft YaHei",
98
- sans-serif;
99
- }
163
+ .blade-chat-host {
164
+ height: 100%;
165
+ display: flex;
166
+ flex-direction: column;
167
+ }
100
168
 
101
- /* 上面两个变量的兜底值必须留在 @supports 外面。不能靠「同一个变量写两遍、让浏览器
102
- * 丢掉无效的那条」降级:自定义属性在声明处不做类型校验,带 cqi 的值在旧浏览器里同样是
103
- * 合法 token、照样覆盖兜底值,失效发生在 padding: var(...) 的使用处——结果是整条 padding
104
- * 作废变成 0,而不是回落到兜底值。 */
105
- @supports (container-type: inline-size) and (width: 1cqi) {
169
+ /* 聊天根容器。SDK 常被嵌进只有 300-400px 宽的侧栏,而宿主页面视口是全宽的,
170
+ * 所以内边距一律按「容器宽度」伸缩,不能用视口断点——那会在窄容器里反向命中最大值。
171
+ * container-type 建在这里(而不是消息区),是为了让消息区和输入区共享同一个 cqi
172
+ * 基准,两者左右边缘才对得齐。代价是内部内容不再向外传递固有宽度,所以宿主需要给
173
+ * 容器一个确定宽度(flex/grid 拉伸或显式宽度都可以)。
174
+ * 底色与文字色也从这里给:根节点上没有对应的 Tailwind class。 */
106
175
  .blade-chat {
107
- --blade-chat-gutter: clamp(0.875rem, 4cqi, 2.5rem);
108
- --blade-chat-turn-gap: clamp(1.25rem, 3cqi, 2.25rem);
176
+ /* 具名容器:宿主或以后的子组件万一在中间又建了个匿名 query container,
177
+ * 下面的 @container 查询不会悄悄改成以那个为基准。 */
178
+ container: blade-chat / inline-size;
179
+ min-width: 0;
180
+ --blade-chat-gutter: 1rem;
181
+ --blade-chat-turn-gap: 1.5rem;
182
+ background: hsl(var(--background));
183
+ color: hsl(var(--foreground));
184
+ font-family:
185
+ system-ui,
186
+ -apple-system,
187
+ "Segoe UI",
188
+ Roboto,
189
+ "PingFang SC",
190
+ "Microsoft YaHei",
191
+ sans-serif;
109
192
  }
110
- }
111
-
112
- .blade-chat-messages {
113
- position: relative;
114
- flex: 1 1 0%;
115
- min-height: 0;
116
- }
117
-
118
- /* 滚动内容的内边距。这一层过去只有 Tailwind class,宿主没装 Tailwind 时消息完全贴边。 */
119
- .blade-chat-messages-scroll {
120
- padding: calc(var(--blade-chat-gutter) * 1.4) var(--blade-chat-gutter)
121
- calc(var(--blade-chat-gutter) * 1.1 + var(--blade-chat-pin-spacer, 0px));
122
- }
123
-
124
- .blade-chat-messages-content {
125
- margin: 0 auto;
126
- max-width: 748px;
127
- }
128
-
129
- .blade-chat-messages-content > div {
130
- display: flex;
131
- flex-direction: column;
132
- gap: var(--blade-chat-turn-gap);
133
- min-width: 0;
134
- }
135
193
 
136
- .blade-chat-empty {
137
- display: flex;
138
- flex-direction: column;
139
- align-items: center;
140
- justify-content: center;
141
- gap: 0.75rem;
142
- /* 这里的双写是有效降级:cqi 出现在属性值里,旧浏览器会丢掉整条声明并回落到上一条。 */
143
- padding: 4rem 0;
144
- padding: clamp(2.5rem, 12cqi, 6rem) 0;
145
- color: hsl(var(--muted-foreground));
146
- }
147
-
148
- .blade-chat-login,
149
- .blade-chat-version-mismatch {
150
- display: flex;
151
- flex: 1 1 0%;
152
- align-items: center;
153
- justify-content: center;
154
- padding: 1.5rem;
155
- }
156
-
157
- /* ---------------------------------------------------------------- 消息气泡 */
158
-
159
- .blade-chat-user-row {
160
- display: flex;
161
- justify-content: flex-end;
162
- }
163
-
164
- .blade-chat-user-col {
165
- display: flex;
166
- flex-direction: column;
167
- align-items: flex-end;
168
- gap: 0.75rem;
169
- max-width: 72%;
170
- }
171
-
172
- /* 窄容器里 72% 只剩 250px 左右,一句话就折三行,这里放宽。
173
- * 这类需要跳变而非连续伸缩的值才用 @container 查询。 */
174
- @container blade-chat (max-width: 480px) {
175
- .blade-chat-user-col {
176
- max-width: 88%;
194
+ /* 上面两个变量的兜底值必须留在 @supports 外面。不能靠「同一个变量写两遍、让浏览器
195
+ * 丢掉无效的那条」降级:自定义属性在声明处不做类型校验,带 cqi 的值在旧浏览器里同样是
196
+ * 合法 token、照样覆盖兜底值,失效发生在 padding: var(...) 的使用处——结果是整条 padding
197
+ * 作废变成 0,而不是回落到兜底值。 */
198
+ @supports (container-type: inline-size) and (width: 1cqi) {
199
+ .blade-chat {
200
+ --blade-chat-gutter: clamp(0.875rem, 4cqi, 2.5rem);
201
+ --blade-chat-turn-gap: clamp(1.25rem, 3cqi, 2.25rem);
202
+ }
177
203
  }
178
204
 
179
- .blade-chat-scroll-bottom-label {
180
- display: none;
205
+ /* 滚动内容的内边距。左右按上面的 gutter 变量走,宿主没装 Tailwind 时消息也不会贴边;
206
+ * 底部那份补白是消息顶置功能的 spacer(--blade-chat-pin-spacer 由组件写行内变量)。 */
207
+ .blade-chat-messages-scroll {
208
+ padding: calc(var(--blade-chat-gutter) * 1.4) var(--blade-chat-gutter)
209
+ calc(var(--blade-chat-gutter) * 1.1 + var(--blade-chat-pin-spacer, 0px));
181
210
  }
182
- }
183
-
184
- .blade-chat-user-bubble {
185
- max-width: 100%;
186
- border: 1px solid hsl(var(--user-msg-border));
187
- background: hsl(var(--user-msg-bg));
188
- color: hsl(var(--user-msg-fg));
189
- border-radius: 20px;
190
- border-bottom-right-radius: 6px;
191
- padding: 13px 18px;
192
- font-size: 0.875rem;
193
- line-height: 1.65;
194
- }
195
-
196
- .blade-chat-assistant-turn {
197
- display: flex;
198
- flex-direction: column;
199
- gap: 0.75rem;
200
- }
201
-
202
- .blade-chat-assistant-text {
203
- font-size: 15px;
204
- line-height: 2;
205
- color: hsl(var(--foreground));
206
- }
207
211
 
208
- .blade-chat-error-row {
209
- display: flex;
210
- justify-content: center;
211
- }
212
-
213
- .blade-chat-error-block {
214
- max-width: 85%;
215
- border-left: 3px solid hsl(var(--border));
216
- padding: 0.25rem 1rem;
217
- font-size: 0.875rem;
218
- line-height: 1.75rem;
219
- color: hsl(var(--muted-foreground));
220
- }
221
-
222
- /* ------------------------------------------------------------- 背景信息卡片 */
212
+ /* 轮次之间的间距用容器宽度算(见 .blade-chat 的说明),挂在内层容器上。 */
213
+ .blade-chat-messages-content > div {
214
+ gap: var(--blade-chat-turn-gap);
215
+ }
223
216
 
224
- .blade-chat-context-card {
225
- overflow: hidden;
226
- border: 1px solid hsl(var(--border));
227
- border-radius: 0.75rem;
228
- background: hsl(var(--card));
229
- }
217
+ .blade-chat-empty {
218
+ display: flex;
219
+ flex-direction: column;
220
+ align-items: center;
221
+ justify-content: center;
222
+ gap: 0.75rem;
223
+ /* 这里的双写是有效降级:cqi 出现在属性值里,旧浏览器会丢掉整条声明并回落到上一条。 */
224
+ padding: 4rem 0;
225
+ padding: clamp(2.5rem, 12cqi, 6rem) 0;
226
+ color: hsl(var(--muted-foreground));
227
+ }
230
228
 
231
- .blade-chat-context-summary {
232
- display: flex;
233
- cursor: pointer;
234
- list-style: none;
235
- align-items: center;
236
- gap: 0.5rem;
237
- padding: 0.625rem 0.75rem;
238
- }
229
+ /* -------------------------------------------------------------- 输入区 */
239
230
 
240
- .blade-chat-context-summary::-webkit-details-marker {
241
- display: none;
242
- }
231
+ /* 左右内边距和消息区共用同一个 gutter,否则输入框与消息文字左边缘对不齐。
232
+ * 上下留白由组件上的 py-3 / pt-0 给。 */
233
+ .blade-chat-input {
234
+ /* 队列面板占位时组件会加上 border-t-0 收起这条线,所以这里不能写死:写死会
235
+ 盖过那个切换。用变量保留默认值,外层仍按原样渲染一条分隔线。 */
236
+ border-top: var(--blade-chat-input-top-border, 1px solid hsl(var(--border)));
237
+ padding-left: var(--blade-chat-gutter);
238
+ padding-right: var(--blade-chat-gutter);
239
+ }
243
240
 
244
- .blade-chat-context-copy {
245
- min-width: 0;
246
- flex: 1 1 0%;
247
- }
241
+ /* 队列面板的外层左右留白:必须跟下面输入框一致,两边各自居中才不会错位。
242
+ * 默认跟随本包输入框用的同一个 gutter 变量;宿主输入框用别的留白时(内置 Web
243
+ * 的输入框外层是 px-5),在自己的样式里覆盖 --blade-chat-queue-gutter 即可。 */
244
+ .blade-chat-queue {
245
+ --blade-chat-queue-gutter: var(--blade-chat-gutter, 1rem);
246
+ padding-left: var(--blade-chat-queue-gutter);
247
+ padding-right: var(--blade-chat-queue-gutter);
248
+ }
248
249
 
249
- .blade-chat-context-title,
250
- .blade-chat-context-status {
251
- display: block;
252
- }
250
+ /* ------------------------------------------------- 无 preflight 时的控件复位 */
253
251
 
254
- .blade-chat-context-title {
255
- color: hsl(var(--foreground));
256
- font-weight: 500;
257
- }
252
+ /* 组件上的工具类不负责这些:宿主只用 style.css 且自己的 Tailwind 关了 preflight 时,
253
+ 输入框和按钮会退回浏览器默认外观(原生边框、原生按钮底色)。 */
254
+ .blade-chat-textarea {
255
+ border: 0;
256
+ font: inherit;
257
+ }
258
258
 
259
- .blade-chat-context-status {
260
- overflow: hidden;
261
- color: hsl(var(--muted-foreground));
262
- font-size: 0.75rem;
263
- text-overflow: ellipsis;
264
- white-space: nowrap;
265
- }
259
+ .blade-chat-input-inner button,
260
+ .blade-chat-codeblock-header button,
261
+ .blade-chat-ask-card button {
262
+ cursor: pointer;
263
+ border: 0;
264
+ background-color: transparent;
265
+ }
266
266
 
267
- /* 180ms + cubic-bezier(0.25,0.1,0.25,1):跟 apps/web 同名组件的
268
- --motion-duration-fast / --motion-ease-enter 数值保持一致(这个包独立发布,
269
- 没有内部 app 的 CSS 变量可用,只能各自写字面量)。 */
270
- .blade-chat-context-chevron {
271
- transition: transform 180ms cubic-bezier(0.25, 0.1, 0.25, 1);
272
- }
267
+ /* ------------------------------------------------------------ 出错条配色 */
273
268
 
274
- /* 只作用于卡片自己的 summary。分组卡片会把单条卡片套在里面,用后代选择器的话
275
- 外层一展开就会把里面所有收起的子卡片的箭头也转过来。 */
276
- .blade-chat-context-card[open] > .blade-chat-context-summary > .blade-chat-context-chevron {
277
- transform: rotate(180deg);
269
+ /* 配色由 .blade-chat-error-bar 的 --destructive 变量给,别写死 red-*:
270
+ * Tailwind 的 dark: 变体跟的是系统偏好,会和 data-theme 主题打架。 */
271
+ .blade-chat-error-bar {
272
+ border-bottom-color: hsl(var(--destructive) / 0.3);
273
+ background: hsl(var(--destructive) / 0.08);
274
+ color: hsl(var(--destructive));
275
+ }
278
276
  }
279
277
 
280
- .blade-chat-context-detail {
281
- border-top: 1px solid hsl(var(--border));
282
- padding: 0.625rem 0.75rem;
283
- color: hsl(var(--muted-foreground));
284
- font-size: 0.75rem;
285
- line-height: 1.25rem;
286
- }
278
+ @layer blade-chat-overrides {
279
+ /* ------------------------------------------------- 窄容器与移动端的手感 */
280
+ /* 需要跳变而非连续伸缩的值才用 @container:窄容器里 72% 只剩 250px 左右,
281
+ * 一句话就折三行。这条必须压过组件上的 max-w-[72%],所以归 blade-chat-overrides。 */
282
+ @container blade-chat (max-width: 480px) {
283
+ .blade-chat-user-col {
284
+ max-width: 88%;
285
+ }
287
286
 
288
- /* 一次输入会注入十几项背景信息,折叠成一行后展开才逐条列出。 */
289
- .blade-chat-context-group-items {
290
- display: flex;
291
- flex-direction: column;
292
- gap: 0.375rem;
293
- border-top: 1px solid hsl(var(--border));
294
- padding: 0.5rem;
295
- }
287
+ .blade-chat-scroll-bottom-label {
288
+ display: none;
289
+ }
290
+ }
296
291
 
297
- .blade-chat-error-bar {
298
- display: flex;
299
- align-items: flex-start;
300
- gap: 0.5rem;
301
- border-bottom: 1px solid hsl(var(--destructive) / 0.3);
302
- background: hsl(var(--destructive) / 0.08);
303
- padding: 0.75rem 1rem;
304
- font-size: 0.875rem;
305
- color: hsl(var(--destructive));
306
- }
292
+ /* Keep embedded ChatView usable on iOS without forcing every host app to know
293
+ Safari's 16px focus-zoom threshold. Desktop sizing remains unchanged.
294
+ text-sm / text-xs 会让输入控件低于 16px,所以这条也归 blade-chat-overrides。 */
295
+ @media (hover: none) and (pointer: coarse) {
296
+ .blade-chat-textarea,
297
+ .blade-chat-ask-card input,
298
+ .blade-chat-ask-card textarea {
299
+ font-size: 1rem;
300
+ }
301
+
302
+ .blade-chat-input-inner button {
303
+ min-width: 44px;
304
+ min-height: 44px;
305
+ }
306
+ }
307
307
 
308
- /* ---------------------------------------------------------------- 输入区 */
309
-
310
- /* 左右内边距和消息区共用同一个 gutter,否则输入框与消息文字左边缘对不齐。 */
311
- .blade-chat-input {
312
- /* 队列面板占位时组件会加上 border-t-0 收起这条线,所以这里不能写死:写死会
313
- 盖过那个切换。用变量保留默认值,外层仍按原样渲染一条分隔线。 */
314
- border-top: var(--blade-chat-input-top-border, 1px solid hsl(var(--border)));
315
- /* 上下留白由组件上的 py-3 / pt-0 给:队列面板贴在它上方时要把上留白去掉,
316
- 写死在这里会盖掉那个切换(内置 Web 的输入框外层同样是 pt-0)。
317
- 左右留白仍走 gutter 变量,跟队列面板共用同一份。 */
318
- padding-left: var(--blade-chat-gutter);
319
- padding-right: var(--blade-chat-gutter);
320
- }
308
+ /* ------------------------------------------------------------- Markdown */
321
309
 
322
- .blade-chat-input-inner {
323
- margin: 0 auto;
324
- display: flex;
325
- align-items: flex-end;
326
- gap: 0.5rem;
327
- max-width: 748px;
328
- border: 1px solid hsl(var(--border));
329
- background: hsl(var(--card));
330
- /* 圆角由组件上的 rounded-2xl / rounded-b-2xl 给:队列面板贴在它上方时
331
- 上两角要变直角,写死在这里会盖掉那个切换。 */
332
- padding: 0.5rem 0.75rem;
333
- }
310
+ /* 正文是 streamdown 生成的 HTML,元素上没有类名可挂,排版只能写在这里。
311
+ * 反过来,带 Tailwind class 的那几个外壳(codeblock、prose 容器)不在这里重复。 */
312
+ .blade-chat-markdown {
313
+ min-width: 0;
314
+ }
334
315
 
335
- /* 队列面板的外层左右留白:必须跟下面输入框一致,两边各自居中才不会错位。
336
- * 默认跟随本包输入框用的同一个 gutter 变量;宿主输入框用别的留白时(内置 Web
337
- * 的输入框外层是 px-5),在自己的样式里覆盖 --blade-chat-queue-gutter 即可。 */
338
- .blade-chat-queue {
339
- --blade-chat-queue-gutter: var(--blade-chat-gutter, 1rem);
340
- padding-left: var(--blade-chat-queue-gutter);
341
- padding-right: var(--blade-chat-queue-gutter);
342
- }
316
+ /* 窄容器里真正把版面撑破的是内容而不是内边距:宽表格、大图、超长 URL 都会把整个
317
+ * 消息区顶宽,然后出现一条横向滚动条。表格自己滚,图片自己缩,长 token 强制断行。 */
318
+ .blade-chat-markdown [data-streamdown="table-wrapper"] {
319
+ max-width: 100%;
320
+ min-width: 0;
321
+ }
343
322
 
344
- .blade-chat-textarea {
345
- flex: 1 1 0%;
346
- resize: none;
347
- border: 0;
348
- background: transparent;
349
- color: hsl(var(--foreground));
350
- font: inherit;
351
- font-size: 0.875rem;
352
- line-height: 1.5rem;
353
- padding: 0.25rem 0;
354
- min-height: 28px;
355
- max-height: 12rem;
356
- outline: none;
357
- }
323
+ /* 横向滚动发生在 table 的父层。streamdown 用 Tailwind 的 overflow-x-auto 实现它,
324
+ * 宿主没装 Tailwind 时那个类是空的,所以这里用普通 CSS 兜住。 */
325
+ .blade-chat-markdown [data-streamdown="table-wrapper"] > div:last-child {
326
+ overflow-x: auto;
327
+ max-width: 100%;
328
+ }
358
329
 
359
- /* Keep embedded ChatView usable on iOS without forcing every host app to know
360
- Safari's 16px focus-zoom threshold. Desktop sizing remains unchanged. */
361
- @media (hover: none) and (pointer: coarse) {
362
- .blade-chat-textarea,
363
- .blade-chat-ask-card input,
364
- .blade-chat-ask-card textarea {
365
- font-size: 1rem;
330
+ .blade-chat-markdown img,
331
+ .blade-chat-markdown video {
332
+ max-width: 100%;
333
+ height: auto;
366
334
  }
367
335
 
368
- .blade-chat-input-inner button {
369
- min-width: 44px;
370
- min-height: 44px;
336
+ /* URL 和长内联代码是撑宽的另外两个常见肇事者。用 anywhere 而不是 break-word:
337
+ * 只有 anywhere 参与 min-content 计算,break-word 挡不住 flex item 被顶宽。 */
338
+ .blade-chat-markdown :not(pre) > code,
339
+ .blade-chat-markdown a {
340
+ overflow-wrap: anywhere;
371
341
  }
372
- }
373
342
 
374
- .blade-chat-input-inner button {
375
- cursor: pointer;
376
- border: 0;
377
- }
343
+ .blade-chat-prose p {
344
+ margin: 0.5em 0;
345
+ }
378
346
 
379
- /* ------------------------------------------------------------ 文件下载卡片 */
347
+ .blade-chat-prose > p:first-child,
348
+ .blade-chat-prose > ul:first-child,
349
+ .blade-chat-prose > ol:first-child {
350
+ margin-top: 0;
351
+ }
380
352
 
381
- .blade-chat-file-card {
382
- display: inline-flex;
383
- max-width: 100%;
384
- margin: 0.25rem 0.5rem 0.25rem 0;
385
- align-items: center;
386
- gap: 0.5rem;
387
- vertical-align: middle;
388
- }
353
+ .blade-chat-prose > p:last-child,
354
+ .blade-chat-prose > ul:last-child,
355
+ .blade-chat-prose > ol:last-child {
356
+ margin-bottom: 0;
357
+ }
389
358
 
390
- .blade-chat-file-card button {
391
- display: inline-flex;
392
- min-width: 0;
393
- align-items: center;
394
- gap: 0.375rem;
395
- cursor: pointer;
396
- border: 1px solid hsl(var(--border));
397
- border-radius: 0.375rem;
398
- background: hsl(var(--card));
399
- color: hsl(var(--card-foreground));
400
- padding: 0.375rem 0.625rem;
401
- font-size: 0.75rem;
402
- font-weight: 500;
403
- line-height: 1.25rem;
404
- text-align: left;
405
- }
359
+ .blade-chat-prose ul,
360
+ .blade-chat-prose ol {
361
+ margin: 0.5em 0;
362
+ padding-left: 1.4em;
363
+ }
406
364
 
407
- .blade-chat-file-card button:hover:not(:disabled) {
408
- background: hsl(var(--accent) / 0.45);
409
- border-color: hsl(var(--primary) / 0.45);
410
- }
365
+ .blade-chat-prose li {
366
+ margin: 0.25em 0;
367
+ }
411
368
 
412
- .blade-chat-file-card button:disabled {
413
- cursor: not-allowed;
414
- opacity: 0.6;
415
- }
369
+ .blade-chat-prose h1 {
370
+ font-size: 1.25em;
371
+ font-weight: 600;
372
+ margin: 1em 0 0.5em;
373
+ }
416
374
 
417
- .blade-chat-file-card-icon {
418
- display: inline-flex;
419
- flex-shrink: 0;
420
- color: hsl(var(--primary));
421
- }
375
+ .blade-chat-prose h2 {
376
+ font-size: 1.15em;
377
+ font-weight: 600;
378
+ margin: 1em 0 0.5em;
379
+ }
422
380
 
423
- /* 窄容器里文件名不能写死上限,否则短名字留白、长名字仍然顶宽。 */
424
- .blade-chat-file-card-name {
425
- overflow: hidden;
426
- text-overflow: ellipsis;
427
- white-space: nowrap;
428
- max-width: 15rem;
429
- max-width: min(15rem, 60cqi);
430
- }
381
+ .blade-chat-prose h3,
382
+ .blade-chat-prose h4 {
383
+ font-size: 1.05em;
384
+ font-weight: 600;
385
+ margin: 0.9em 0 0.4em;
386
+ }
431
387
 
432
- .blade-chat-file-card-action {
433
- flex-shrink: 0;
434
- color: hsl(var(--muted-foreground));
435
- }
388
+ .blade-chat-prose blockquote {
389
+ border-left: 3px solid hsl(var(--border));
390
+ margin: 0.5em 0;
391
+ padding-left: 0.9em;
392
+ color: hsl(var(--muted-foreground));
393
+ }
436
394
 
437
- .blade-chat-file-card-error {
438
- font-size: 0.6875rem;
439
- color: hsl(var(--destructive));
440
- }
395
+ .blade-chat-prose a {
396
+ color: hsl(var(--primary));
397
+ text-decoration: underline;
398
+ }
441
399
 
442
- /* ---------------------------------------------------------------- 工具块 */
400
+ .blade-chat-prose code {
401
+ background: hsl(var(--muted) / 0.6);
402
+ border-radius: 0.25rem;
403
+ padding: 0.1em 0.35em;
404
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
405
+ font-size: 0.9em;
406
+ }
443
407
 
444
- .blade-chat-tool,
445
- .blade-chat-agent-loop,
446
- .blade-chat-thinking {
447
- margin-left: 1rem;
448
- font-size: 0.75rem;
449
- }
408
+ .blade-chat-prose table {
409
+ border-collapse: collapse;
410
+ margin: 0.75em 0;
411
+ width: 100%;
412
+ }
450
413
 
451
- .blade-chat-tool-detail pre {
452
- overflow: auto;
453
- white-space: pre-wrap;
454
- border-radius: 0.375rem;
455
- background: hsl(var(--muted));
456
- padding: 0.5rem;
457
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
458
- font-size: 11px;
459
- color: hsl(var(--foreground));
460
- }
414
+ .blade-chat-prose th,
415
+ .blade-chat-prose td {
416
+ border: 1px solid hsl(var(--border));
417
+ padding: 0.4em 0.7em;
418
+ text-align: left;
419
+ }
461
420
 
462
- /* ---------------------------------------------------------------- Markdown */
421
+ /* 代码块正文(<pre>)的排版:外层 codeblock 外壳与滚动由组件上的类给,
422
+ * 这里只补等宽字体和底色。 */
423
+ .blade-chat-codeblock-body {
424
+ background: hsl(var(--muted) / 0.25);
425
+ padding: 0.875rem 1rem;
426
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
427
+ font-size: 13.5px;
428
+ line-height: 1.7;
429
+ }
463
430
 
464
- .blade-chat-markdown {
465
- overflow-wrap: break-word;
466
- min-width: 0;
467
- }
431
+ /* 代码块里的内联 code 不再套一层底色和留白。 */
432
+ .blade-chat-codeblock-body code {
433
+ background: transparent;
434
+ padding: 0;
435
+ }
468
436
 
469
- /* 窄容器里真正把版面撑破的是内容而不是内边距:宽表格、大图、超长 URL 都会把整个
470
- * 消息区顶宽,然后出现一条横向滚动条。表格自己滚,图片自己缩,长 token 强制断行。 */
471
- .blade-chat-markdown [data-streamdown="table-wrapper"] {
472
- max-width: 100%;
473
- min-width: 0;
474
- }
437
+ /* ---------------------------------------------------------------- 动效 */
438
+
439
+ .blade-shimmer-text {
440
+ background: linear-gradient(
441
+ 90deg,
442
+ hsl(var(--muted-foreground)) 0%,
443
+ hsl(var(--foreground)) 50%,
444
+ hsl(var(--muted-foreground)) 100%
445
+ );
446
+ background-size: 200% 100%;
447
+ -webkit-background-clip: text;
448
+ background-clip: text;
449
+ -webkit-text-fill-color: transparent;
450
+ color: transparent;
451
+ animation: blade-shimmer 2s linear infinite;
452
+ }
475
453
 
476
- /* 横向滚动发生在 table 的父层。streamdown 用 Tailwind 的 overflow-x-auto 实现它,
477
- * 宿主没装 Tailwind 时那个类是空的,所以这里用普通 CSS 兜住。 */
478
- .blade-chat-markdown [data-streamdown="table-wrapper"] > div:last-child {
479
- overflow-x: auto;
480
- max-width: 100%;
454
+ @keyframes blade-shimmer {
455
+ from {
456
+ background-position: 200% 0;
457
+ }
458
+ to {
459
+ background-position: -200% 0;
460
+ }
461
+ }
481
462
  }
482
463
 
483
- .blade-chat-markdown img,
484
- .blade-chat-markdown video {
485
- max-width: 100%;
486
- height: auto;
487
- }
464
+ /* ------------------------------------------------------------ 消息导航轨 */
488
465
 
489
- /* 长 URL 和长内联代码是撑宽的另外两个常见肇事者。用 anywhere 而不是 break-word:
490
- * 只有 anywhere 参与 min-content 计算,break-word 挡不住 flex item 被顶宽。 */
491
- .blade-chat-markdown :not(pre) > code,
492
- .blade-chat-markdown a {
493
- overflow-wrap: anywhere;
466
+ /* 右侧的发言刻度轨。定位绝对:悬停才展开的目录不能占布局宽度,否则同一行的
467
+ * 内容会被挤窄,而不悬停时那块空间永远是空的。 */
468
+ .blade-turn-nav {
469
+ position: absolute;
470
+ right: 0.5rem;
471
+ top: 50%;
472
+ transform: translateY(-50%);
473
+ z-index: 30;
494
474
  }
495
475
 
496
- .blade-chat-prose p {
497
- margin: 0.5em 0;
476
+ .blade-turn-nav-markers {
477
+ display: flex;
478
+ flex-direction: column;
479
+ align-items: flex-end;
480
+ max-height: 20rem;
481
+ /* 条数超过可视高度时要能滚:固定 hidden 会让后面的刻度永远点不到。
482
+ 滚动条本身藏掉,它只是给长会话兜底。 */
483
+ overflow-y: auto;
484
+ scrollbar-width: none;
485
+ padding: 0.375rem 0.375rem 0.375rem 0;
486
+ -webkit-mask-image: linear-gradient(
487
+ to bottom,
488
+ transparent,
489
+ black 10%,
490
+ black 90%,
491
+ transparent
492
+ );
493
+ mask-image: linear-gradient(to bottom, transparent, black 10%, black 90%, transparent);
498
494
  }
499
495
 
500
- .blade-chat-prose > p:first-child,
501
- .blade-chat-prose > ul:first-child,
502
- .blade-chat-prose > ol:first-child {
503
- margin-top: 0;
496
+ .blade-turn-nav-markers::-webkit-scrollbar {
497
+ display: none;
504
498
  }
505
499
 
506
- .blade-chat-prose > p:last-child,
507
- .blade-chat-prose > ul:last-child,
508
- .blade-chat-prose > ol:last-child {
509
- margin-bottom: 0;
500
+ .blade-turn-nav-spacer-top {
501
+ margin-top: auto;
510
502
  }
511
-
512
- .blade-chat-prose ul,
513
- .blade-chat-prose ol {
514
- margin: 0.5em 0;
515
- padding-left: 1.4em;
503
+ .blade-turn-nav-spacer-bottom {
504
+ margin-bottom: auto;
516
505
  }
517
506
 
518
- .blade-chat-prose li {
519
- margin: 0.25em 0;
507
+ .blade-turn-nav-tick {
508
+ display: flex;
509
+ align-items: center;
510
+ height: 0.875rem;
511
+ flex-shrink: 0;
512
+ padding: 0;
513
+ border: 0;
514
+ background: none;
515
+ cursor: pointer;
520
516
  }
521
517
 
522
- .blade-chat-prose h1 {
523
- font-size: 1.25em;
524
- font-weight: 600;
525
- margin: 1em 0 0.5em;
518
+ .blade-turn-nav-tick > span {
519
+ display: block;
520
+ width: 0.75rem;
521
+ height: 0.125rem;
522
+ border-radius: 9999px;
523
+ background: hsl(var(--muted-foreground) / 0.35);
524
+ transition: width 0.15s ease, height 0.15s ease, background-color 0.15s ease;
526
525
  }
527
526
 
528
- .blade-chat-prose h2 {
529
- font-size: 1.15em;
530
- font-weight: 600;
531
- margin: 1em 0 0.5em;
527
+ .blade-turn-nav-tick[data-hovered="true"] > span {
528
+ width: 1.25rem;
529
+ height: 0.25rem;
530
+ background: hsl(var(--muted-foreground) / 0.7);
532
531
  }
533
532
 
534
- .blade-chat-prose h3,
535
- .blade-chat-prose h4 {
536
- font-size: 1.05em;
537
- font-weight: 600;
538
- margin: 0.9em 0 0.4em;
533
+ .blade-turn-nav-tick[data-active="true"] > span {
534
+ width: 1.25rem;
535
+ height: 0.25rem;
536
+ background: hsl(var(--foreground));
539
537
  }
540
538
 
541
- .blade-chat-prose blockquote {
542
- border-left: 3px solid hsl(var(--border));
543
- margin: 0.5em 0;
544
- padding-left: 0.9em;
545
- color: hsl(var(--muted-foreground));
539
+ /* 定位的进度与失败提示:贴在刻度轨左侧,不随面板开合。 */
540
+ .blade-turn-nav-status {
541
+ position: absolute;
542
+ right: 100%;
543
+ top: 50%;
544
+ transform: translateY(-50%);
545
+ display: flex;
546
+ align-items: center;
547
+ gap: 0.5rem;
548
+ margin-right: 0.5rem;
549
+ padding: 0.25rem 0.625rem;
550
+ border: 1px solid hsl(var(--border));
551
+ border-radius: 9999px;
552
+ background: hsl(var(--popover));
553
+ color: hsl(var(--popover-foreground));
554
+ font-size: 0.75rem;
555
+ white-space: nowrap;
556
+ box-shadow: 0 4px 14px hsl(var(--foreground) / 0.12);
546
557
  }
547
558
 
548
- .blade-chat-prose a {
559
+ .blade-turn-nav-status button {
560
+ border: 0;
561
+ background: none;
562
+ padding: 0;
549
563
  color: hsl(var(--primary));
564
+ font-size: 0.75rem;
565
+ cursor: pointer;
550
566
  text-decoration: underline;
551
567
  }
552
568
 
553
- .blade-chat-prose code {
554
- background: hsl(var(--muted) / 0.6);
555
- border-radius: 0.25rem;
556
- padding: 0.1em 0.35em;
557
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
558
- font-size: 0.9em;
569
+ .blade-turn-nav-panel {
570
+ position: absolute;
571
+ right: 100%;
572
+ /* 与刻度轨同心:顶对齐会让一个接近上限高度的面板整条往下长,聊天区是 overflow: hidden,
573
+ 视口矮一点下面的条目就被裁掉、连滚都滚不到(滚动容器以为自己有完整高度)。 */
574
+ top: 50%;
575
+ transform: translateY(-50%);
576
+ padding-right: 0.5rem;
559
577
  }
560
578
 
561
- .blade-chat-prose table {
562
- border-collapse: collapse;
563
- margin: 0.75em 0;
564
- width: 100%;
565
- }
566
-
567
- .blade-chat-prose th,
568
- .blade-chat-prose td {
579
+ .blade-turn-nav-list {
580
+ display: flex;
581
+ flex-direction: column;
582
+ width: 20rem;
583
+ /* 高度上限同时受视口约束,短视口里面板整体缩进去而不是被裁掉。 */
584
+ max-height: min(28rem, 60vh);
585
+ overflow-y: auto;
586
+ padding: 0.25rem 0;
569
587
  border: 1px solid hsl(var(--border));
570
- padding: 0.4em 0.7em;
571
- text-align: left;
588
+ border-radius: 0.5rem;
589
+ background: hsl(var(--popover));
590
+ box-shadow: 0 10px 30px hsl(var(--foreground) / 0.18);
572
591
  }
573
592
 
574
- .blade-chat-codeblock {
575
- margin: 0.75rem 0;
593
+ .blade-turn-nav-item {
594
+ flex-shrink: 0;
595
+ padding: 0.375rem 0.75rem;
596
+ border: 0;
597
+ background: none;
598
+ text-align: left;
599
+ font-size: 0.75rem;
600
+ line-height: 1.25rem;
601
+ color: hsl(var(--popover-foreground));
602
+ white-space: nowrap;
576
603
  overflow: hidden;
577
- border: 1px solid hsl(var(--border));
578
- border-radius: 0.75rem;
579
- }
580
-
581
- .blade-chat-codeblock-header {
582
- display: flex;
583
- align-items: center;
584
- justify-content: space-between;
585
- height: 34px;
586
- border-bottom: 1px solid hsl(var(--border));
587
- background: hsl(var(--muted) / 0.5);
588
- padding: 0 0.375rem 0 0.875rem;
589
- color: hsl(var(--muted-foreground));
590
- }
591
-
592
- .blade-chat-codeblock-header button {
604
+ text-overflow: ellipsis;
593
605
  cursor: pointer;
594
- border: 0;
595
- background: transparent;
596
- display: inline-flex;
597
- align-items: center;
598
- gap: 0.25rem;
599
- color: inherit;
600
606
  }
601
607
 
602
- .blade-chat-codeblock-body {
603
- margin: 0;
604
- overflow-x: auto;
605
- background: hsl(var(--muted) / 0.25);
606
- padding: 0.875rem 1rem;
607
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
608
- font-size: 13.5px;
609
- line-height: 1.7;
608
+ .blade-turn-nav-item[data-hovered="true"],
609
+ .blade-turn-nav-item[data-active="true"] {
610
+ background: hsl(var(--accent));
610
611
  }
611
612
 
612
- .blade-chat-codeblock-body code {
613
- background: transparent;
614
- padding: 0;
613
+ /* 定位到某条发言后的短暂高亮。加到消息行本身(data-entry-id 那一层),
614
+ * 到点由 hook 摘掉类名,不改变滚动位置。内置 Web 的 app.css 有同名镜像。 */
615
+ .blade-turn-nav-flash {
616
+ animation: blade-turn-nav-flash 1.6s ease-out both;
617
+ border-radius: 0.5rem;
615
618
  }
616
619
 
617
- /* ---------------------------------------------------------------- 动效 */
618
-
619
- .blade-shimmer-text {
620
- background: linear-gradient(
621
- 90deg,
622
- hsl(var(--muted-foreground)) 0%,
623
- hsl(var(--foreground)) 50%,
624
- hsl(var(--muted-foreground)) 100%
625
- );
626
- background-size: 200% 100%;
627
- -webkit-background-clip: text;
628
- background-clip: text;
629
- -webkit-text-fill-color: transparent;
630
- color: transparent;
631
- animation: blade-shimmer 2s linear infinite;
620
+ /* 减少动效:保留高亮但不动画,用户仍然看得见"定位到哪一条"。 */
621
+ @media (prefers-reduced-motion: reduce) {
622
+ .blade-turn-nav-flash {
623
+ animation: none;
624
+ background-color: hsl(var(--accent) / 0.75);
625
+ }
632
626
  }
633
627
 
634
- @keyframes blade-shimmer {
628
+ @keyframes blade-turn-nav-flash {
635
629
  from {
636
- background-position: 200% 0;
630
+ background-color: hsl(var(--accent) / 0.75);
637
631
  }
638
632
  to {
639
- background-position: -200% 0;
633
+ background-color: transparent;
640
634
  }
641
635
  }