@blade-hq/agent-react 2610.0.0-beta.7 → 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 (64) hide show
  1. package/README.md +308 -8
  2. package/dist/chunk-G3PMV62Z.js +36 -0
  3. package/dist/{chunk-ZXXLY4RM.js → chunk-IA6J2NTB.js} +9 -32
  4. package/dist/{chunk-ZXXLY4RM.js.map → chunk-IA6J2NTB.js.map} +1 -1
  5. package/dist/components/AgentChat.d.ts +3 -0
  6. package/dist/components/AgentLoopBlock.d.ts +3 -5
  7. package/dist/components/AskUserQuestionBlock.d.ts +12 -1
  8. package/dist/components/AssistantTurnBlock.d.ts +26 -8
  9. package/dist/components/ChatInput.d.ts +21 -1
  10. package/dist/components/ChatSurface.d.ts +133 -2
  11. package/dist/components/ChatView.d.ts +1 -1
  12. package/dist/components/ConnectorPanel.d.ts +41 -0
  13. package/dist/components/ContextCard.d.ts +20 -0
  14. package/dist/components/MarkdownContent.d.ts +2 -0
  15. package/dist/components/McpAppCard.d.ts +14 -0
  16. package/dist/components/MessageList.d.ts +38 -4
  17. package/dist/components/PlanUpdateBlock.d.ts +32 -0
  18. package/dist/components/PluginConnectorList.d.ts +47 -0
  19. package/dist/components/PluginTriggerStack.d.ts +22 -0
  20. package/dist/components/PostChatFollowupBlock.d.ts +5 -1
  21. package/dist/components/SessionMemoryToggle.d.ts +19 -0
  22. package/dist/components/SessionPluginConfigDialog.d.ts +10 -0
  23. package/dist/components/SessionPluginConfigForm.d.ts +14 -0
  24. package/dist/components/SessionPluginIcon.d.ts +30 -0
  25. package/dist/components/SessionPluginSelector.d.ts +10 -0
  26. package/dist/components/SessionQueuePanel.d.ts +74 -0
  27. package/dist/components/ToolCallBlock.d.ts +9 -3
  28. package/dist/components/ToolUiCardView.d.ts +9 -0
  29. package/dist/components/TurnNavRail.d.ts +23 -0
  30. package/dist/components/UserMessageBubble.d.ts +7 -3
  31. package/dist/components/WhatIfUserBubble.d.ts +7 -0
  32. package/dist/components/connector-panel-types.d.ts +37 -0
  33. package/dist/components/display-utils.d.ts +10 -0
  34. package/dist/components/plugin-config-draft.d.ts +13 -0
  35. package/dist/components/plugin-config-schema.d.ts +6 -0
  36. package/dist/components/plugin-connector.d.ts +40 -0
  37. package/dist/components/queue-panel-adjacency.d.ts +1 -0
  38. package/dist/components/use-session-plugin-activation.d.ts +40 -0
  39. package/dist/context.d.ts +1 -0
  40. package/dist/embed/blade-chat-element.d.ts +4 -3
  41. package/dist/embed/entry.d.ts +48 -0
  42. package/dist/{highlighted-body-B3W2YXNL-YD7FAP6V.js → highlighted-body-B3W2YXNL-YF4GVJGP.js} +3 -2
  43. package/dist/{highlighted-body-B3W2YXNL-YD7FAP6V.js.map → highlighted-body-B3W2YXNL-YF4GVJGP.js.map} +1 -1
  44. package/dist/hooks/use-agent-session.d.ts +5 -0
  45. package/dist/hooks/use-message-pin.d.ts +28 -0
  46. package/dist/hooks/use-turn-navigation.d.ts +117 -0
  47. package/dist/hooks/use-typewriter-reveal.d.ts +32 -0
  48. package/dist/index.d.ts +49 -1
  49. package/dist/index.js +45645 -1787
  50. package/dist/index.js.map +1 -1
  51. package/dist/lib/agent-computer-command.d.ts +34 -0
  52. package/dist/lib/random-id.d.ts +10 -0
  53. package/dist/lib/utils.d.ts +17 -2
  54. package/dist/lib/whatif-prompt.d.ts +18 -0
  55. package/dist/mermaid-3ZIDBTTL-QL4YGD6I.js +9 -0
  56. package/dist/mermaid-3ZIDBTTL-QL4YGD6I.js.map +1 -0
  57. package/dist/style.css +488 -402
  58. package/dist/style.full.css +499 -406
  59. package/dist/webapi-W3IB5DO4.js +2481 -0
  60. package/dist/webapi-W3IB5DO4.js.map +1 -0
  61. package/package.json +2 -2
  62. package/public-api.md +1657 -76
  63. package/dist/mermaid-3ZIDBTTL-N7YKJ3SM.js +0 -8
  64. /package/dist/{mermaid-3ZIDBTTL-N7YKJ3SM.js.map → chunk-G3PMV62Z.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`。
7
17
  */
8
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
89
+ */
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,496 +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
193
 
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);
122
- }
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
+ }
203
+ }
123
204
 
124
- .blade-chat-messages-content {
125
- margin: 0 auto;
126
- max-width: 748px;
127
- }
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));
210
+ }
128
211
 
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
- }
212
+ /* 轮次之间的间距用容器宽度算(见 .blade-chat 的说明),挂在内层容器上。 */
213
+ .blade-chat-messages-content > div {
214
+ gap: var(--blade-chat-turn-gap);
215
+ }
135
216
 
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
- }
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
+ }
147
228
 
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
- }
229
+ /* -------------------------------------------------------------- 输入区 */
156
230
 
157
- /* ---------------------------------------------------------------- 消息气泡 */
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
+ }
158
240
 
159
- .blade-chat-user-row {
160
- display: flex;
161
- justify-content: flex-end;
162
- }
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
+ }
163
249
 
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
- }
250
+ /* ------------------------------------------------- 无 preflight 时的控件复位 */
171
251
 
172
- /* 窄容器里 72% 只剩 250px 左右,一句话就折三行,这里放宽。
173
- * 这类需要跳变而非连续伸缩的值才用 @container 查询。 */
174
- @container blade-chat (max-width: 480px) {
175
- .blade-chat-user-col {
176
- max-width: 88%;
252
+ /* 组件上的工具类不负责这些:宿主只用 style.css 且自己的 Tailwind 关了 preflight 时,
253
+ 输入框和按钮会退回浏览器默认外观(原生边框、原生按钮底色)。 */
254
+ .blade-chat-textarea {
255
+ border: 0;
256
+ font: inherit;
177
257
  }
178
258
 
179
- .blade-chat-scroll-bottom-label {
180
- display: none;
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;
181
265
  }
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
266
 
196
- .blade-chat-assistant-turn {
197
- display: flex;
198
- flex-direction: column;
199
- gap: 0.75rem;
200
- }
267
+ /* ------------------------------------------------------------ 出错条配色 */
201
268
 
202
- .blade-chat-assistant-text {
203
- font-size: 15px;
204
- line-height: 2;
205
- color: hsl(var(--foreground));
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
+ }
206
276
  }
207
277
 
208
- .blade-chat-error-row {
209
- display: flex;
210
- justify-content: center;
211
- }
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
+ }
212
286
 
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
- }
287
+ .blade-chat-scroll-bottom-label {
288
+ display: none;
289
+ }
290
+ }
221
291
 
222
- .blade-chat-error-bar {
223
- display: flex;
224
- align-items: flex-start;
225
- gap: 0.5rem;
226
- border-bottom: 1px solid hsl(var(--destructive) / 0.3);
227
- background: hsl(var(--destructive) / 0.08);
228
- padding: 0.75rem 1rem;
229
- font-size: 0.875rem;
230
- color: hsl(var(--destructive));
231
- }
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
+ }
232
307
 
233
- /* ---------------------------------------------------------------- 输入区 */
308
+ /* ------------------------------------------------------------- Markdown */
234
309
 
235
- /* 左右内边距和消息区共用同一个 gutter,否则输入框与消息文字左边缘对不齐。 */
236
- .blade-chat-input {
237
- border-top: 1px solid hsl(var(--border));
238
- padding: 0.75rem var(--blade-chat-gutter);
239
- }
310
+ /* 正文是 streamdown 生成的 HTML,元素上没有类名可挂,排版只能写在这里。
311
+ * 反过来,带 Tailwind class 的那几个外壳(codeblock、prose 容器)不在这里重复。 */
312
+ .blade-chat-markdown {
313
+ min-width: 0;
314
+ }
240
315
 
241
- .blade-chat-input-inner {
242
- margin: 0 auto;
243
- display: flex;
244
- align-items: flex-end;
245
- gap: 0.5rem;
246
- max-width: 748px;
247
- border: 1px solid hsl(var(--border));
248
- background: hsl(var(--card));
249
- border-radius: 1rem;
250
- padding: 0.5rem 0.75rem;
251
- }
316
+ /* 窄容器里真正把版面撑破的是内容而不是内边距:宽表格、大图、超长 URL 都会把整个
317
+ * 消息区顶宽,然后出现一条横向滚动条。表格自己滚,图片自己缩,长 token 强制断行。 */
318
+ .blade-chat-markdown [data-streamdown="table-wrapper"] {
319
+ max-width: 100%;
320
+ min-width: 0;
321
+ }
252
322
 
253
- .blade-chat-textarea {
254
- flex: 1 1 0%;
255
- resize: none;
256
- border: 0;
257
- background: transparent;
258
- color: hsl(var(--foreground));
259
- font: inherit;
260
- font-size: 0.875rem;
261
- line-height: 1.5rem;
262
- padding: 0.25rem 0;
263
- min-height: 28px;
264
- max-height: 12rem;
265
- outline: none;
266
- }
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
+ }
267
329
 
268
- /* Keep embedded ChatView usable on iOS without forcing every host app to know
269
- Safari's 16px focus-zoom threshold. Desktop sizing remains unchanged. */
270
- @media (hover: none) and (pointer: coarse) {
271
- .blade-chat-textarea,
272
- .blade-chat-ask-card input {
273
- font-size: 1rem;
330
+ .blade-chat-markdown img,
331
+ .blade-chat-markdown video {
332
+ max-width: 100%;
333
+ height: auto;
274
334
  }
275
335
 
276
- .blade-chat-input-inner button {
277
- min-width: 44px;
278
- 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;
279
341
  }
280
- }
281
342
 
282
- .blade-chat-input-inner button {
283
- cursor: pointer;
284
- border: 0;
285
- }
343
+ .blade-chat-prose p {
344
+ margin: 0.5em 0;
345
+ }
286
346
 
287
- /* ------------------------------------------------------------ 文件下载卡片 */
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
+ }
288
352
 
289
- .blade-chat-file-card {
290
- display: inline-flex;
291
- max-width: 100%;
292
- margin: 0.25rem 0.5rem 0.25rem 0;
293
- align-items: center;
294
- gap: 0.5rem;
295
- vertical-align: middle;
296
- }
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
+ }
297
358
 
298
- .blade-chat-file-card button {
299
- display: inline-flex;
300
- min-width: 0;
301
- align-items: center;
302
- gap: 0.375rem;
303
- cursor: pointer;
304
- border: 1px solid hsl(var(--border));
305
- border-radius: 0.375rem;
306
- background: hsl(var(--card));
307
- color: hsl(var(--card-foreground));
308
- padding: 0.375rem 0.625rem;
309
- font-size: 0.75rem;
310
- font-weight: 500;
311
- line-height: 1.25rem;
312
- text-align: left;
313
- }
359
+ .blade-chat-prose ul,
360
+ .blade-chat-prose ol {
361
+ margin: 0.5em 0;
362
+ padding-left: 1.4em;
363
+ }
314
364
 
315
- .blade-chat-file-card button:hover:not(:disabled) {
316
- background: hsl(var(--accent) / 0.45);
317
- border-color: hsl(var(--primary) / 0.45);
318
- }
365
+ .blade-chat-prose li {
366
+ margin: 0.25em 0;
367
+ }
319
368
 
320
- .blade-chat-file-card button:disabled {
321
- cursor: not-allowed;
322
- opacity: 0.6;
323
- }
369
+ .blade-chat-prose h1 {
370
+ font-size: 1.25em;
371
+ font-weight: 600;
372
+ margin: 1em 0 0.5em;
373
+ }
324
374
 
325
- .blade-chat-file-card-icon {
326
- display: inline-flex;
327
- flex-shrink: 0;
328
- color: hsl(var(--primary));
329
- }
375
+ .blade-chat-prose h2 {
376
+ font-size: 1.15em;
377
+ font-weight: 600;
378
+ margin: 1em 0 0.5em;
379
+ }
330
380
 
331
- /* 窄容器里文件名不能写死上限,否则短名字留白、长名字仍然顶宽。 */
332
- .blade-chat-file-card-name {
333
- overflow: hidden;
334
- text-overflow: ellipsis;
335
- white-space: nowrap;
336
- max-width: 15rem;
337
- max-width: min(15rem, 60cqi);
338
- }
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
+ }
339
387
 
340
- .blade-chat-file-card-action {
341
- flex-shrink: 0;
342
- color: hsl(var(--muted-foreground));
343
- }
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
+ }
344
394
 
345
- .blade-chat-file-card-error {
346
- font-size: 0.6875rem;
347
- color: hsl(var(--destructive));
348
- }
395
+ .blade-chat-prose a {
396
+ color: hsl(var(--primary));
397
+ text-decoration: underline;
398
+ }
349
399
 
350
- /* ---------------------------------------------------------------- 工具块 */
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
+ }
351
407
 
352
- .blade-chat-tool,
353
- .blade-chat-agent-loop,
354
- .blade-chat-thinking {
355
- margin-left: 1rem;
356
- font-size: 0.75rem;
357
- }
408
+ .blade-chat-prose table {
409
+ border-collapse: collapse;
410
+ margin: 0.75em 0;
411
+ width: 100%;
412
+ }
358
413
 
359
- .blade-chat-tool-detail pre {
360
- overflow: auto;
361
- white-space: pre-wrap;
362
- border-radius: 0.375rem;
363
- background: hsl(var(--muted));
364
- padding: 0.5rem;
365
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
366
- font-size: 11px;
367
- color: hsl(var(--foreground));
368
- }
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
+ }
369
420
 
370
- /* ---------------------------------------------------------------- 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
+ }
371
430
 
372
- .blade-chat-markdown {
373
- overflow-wrap: break-word;
374
- min-width: 0;
375
- }
431
+ /* 代码块里的内联 code 不再套一层底色和留白。 */
432
+ .blade-chat-codeblock-body code {
433
+ background: transparent;
434
+ padding: 0;
435
+ }
376
436
 
377
- /* 窄容器里真正把版面撑破的是内容而不是内边距:宽表格、大图、超长 URL 都会把整个
378
- * 消息区顶宽,然后出现一条横向滚动条。表格自己滚,图片自己缩,长 token 强制断行。 */
379
- .blade-chat-markdown [data-streamdown="table-wrapper"] {
380
- max-width: 100%;
381
- min-width: 0;
382
- }
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
+ }
383
453
 
384
- /* 横向滚动发生在 table 的父层。streamdown 用 Tailwind 的 overflow-x-auto 实现它,
385
- * 宿主没装 Tailwind 时那个类是空的,所以这里用普通 CSS 兜住。 */
386
- .blade-chat-markdown [data-streamdown="table-wrapper"] > div:last-child {
387
- overflow-x: auto;
388
- max-width: 100%;
454
+ @keyframes blade-shimmer {
455
+ from {
456
+ background-position: 200% 0;
457
+ }
458
+ to {
459
+ background-position: -200% 0;
460
+ }
461
+ }
389
462
  }
390
463
 
391
- .blade-chat-markdown img,
392
- .blade-chat-markdown video {
393
- max-width: 100%;
394
- height: auto;
395
- }
464
+ /* ------------------------------------------------------------ 消息导航轨 */
396
465
 
397
- /* 长 URL 和长内联代码是撑宽的另外两个常见肇事者。用 anywhere 而不是 break-word:
398
- * 只有 anywhere 参与 min-content 计算,break-word 挡不住 flex item 被顶宽。 */
399
- .blade-chat-markdown :not(pre) > code,
400
- .blade-chat-markdown a {
401
- 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;
402
474
  }
403
475
 
404
- .blade-chat-prose p {
405
- 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);
406
494
  }
407
495
 
408
- .blade-chat-prose > p:first-child,
409
- .blade-chat-prose > ul:first-child,
410
- .blade-chat-prose > ol:first-child {
411
- margin-top: 0;
496
+ .blade-turn-nav-markers::-webkit-scrollbar {
497
+ display: none;
412
498
  }
413
499
 
414
- .blade-chat-prose > p:last-child,
415
- .blade-chat-prose > ul:last-child,
416
- .blade-chat-prose > ol:last-child {
417
- margin-bottom: 0;
500
+ .blade-turn-nav-spacer-top {
501
+ margin-top: auto;
418
502
  }
419
-
420
- .blade-chat-prose ul,
421
- .blade-chat-prose ol {
422
- margin: 0.5em 0;
423
- padding-left: 1.4em;
503
+ .blade-turn-nav-spacer-bottom {
504
+ margin-bottom: auto;
424
505
  }
425
506
 
426
- .blade-chat-prose li {
427
- 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;
428
516
  }
429
517
 
430
- .blade-chat-prose h1 {
431
- font-size: 1.25em;
432
- font-weight: 600;
433
- 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;
434
525
  }
435
526
 
436
- .blade-chat-prose h2 {
437
- font-size: 1.15em;
438
- font-weight: 600;
439
- 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);
440
531
  }
441
532
 
442
- .blade-chat-prose h3,
443
- .blade-chat-prose h4 {
444
- font-size: 1.05em;
445
- font-weight: 600;
446
- 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));
447
537
  }
448
538
 
449
- .blade-chat-prose blockquote {
450
- border-left: 3px solid hsl(var(--border));
451
- margin: 0.5em 0;
452
- padding-left: 0.9em;
453
- 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);
454
557
  }
455
558
 
456
- .blade-chat-prose a {
559
+ .blade-turn-nav-status button {
560
+ border: 0;
561
+ background: none;
562
+ padding: 0;
457
563
  color: hsl(var(--primary));
564
+ font-size: 0.75rem;
565
+ cursor: pointer;
458
566
  text-decoration: underline;
459
567
  }
460
568
 
461
- .blade-chat-prose code {
462
- background: hsl(var(--muted) / 0.6);
463
- border-radius: 0.25rem;
464
- padding: 0.1em 0.35em;
465
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
466
- 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;
467
577
  }
468
578
 
469
- .blade-chat-prose table {
470
- border-collapse: collapse;
471
- margin: 0.75em 0;
472
- width: 100%;
473
- }
474
-
475
- .blade-chat-prose th,
476
- .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;
477
587
  border: 1px solid hsl(var(--border));
478
- padding: 0.4em 0.7em;
479
- text-align: left;
588
+ border-radius: 0.5rem;
589
+ background: hsl(var(--popover));
590
+ box-shadow: 0 10px 30px hsl(var(--foreground) / 0.18);
480
591
  }
481
592
 
482
- .blade-chat-codeblock {
483
- 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;
484
603
  overflow: hidden;
485
- border: 1px solid hsl(var(--border));
486
- border-radius: 0.75rem;
487
- }
488
-
489
- .blade-chat-codeblock-header {
490
- display: flex;
491
- align-items: center;
492
- justify-content: space-between;
493
- height: 34px;
494
- border-bottom: 1px solid hsl(var(--border));
495
- background: hsl(var(--muted) / 0.5);
496
- padding: 0 0.375rem 0 0.875rem;
497
- color: hsl(var(--muted-foreground));
498
- }
499
-
500
- .blade-chat-codeblock-header button {
604
+ text-overflow: ellipsis;
501
605
  cursor: pointer;
502
- border: 0;
503
- background: transparent;
504
- display: inline-flex;
505
- align-items: center;
506
- gap: 0.25rem;
507
- color: inherit;
508
606
  }
509
607
 
510
- .blade-chat-codeblock-body {
511
- margin: 0;
512
- overflow-x: auto;
513
- background: hsl(var(--muted) / 0.25);
514
- padding: 0.875rem 1rem;
515
- font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
516
- font-size: 13.5px;
517
- 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));
518
611
  }
519
612
 
520
- .blade-chat-codeblock-body code {
521
- background: transparent;
522
- 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;
523
618
  }
524
619
 
525
- /* ---------------------------------------------------------------- 动效 */
526
-
527
- .blade-shimmer-text {
528
- background: linear-gradient(
529
- 90deg,
530
- hsl(var(--muted-foreground)) 0%,
531
- hsl(var(--foreground)) 50%,
532
- hsl(var(--muted-foreground)) 100%
533
- );
534
- background-size: 200% 100%;
535
- -webkit-background-clip: text;
536
- background-clip: text;
537
- -webkit-text-fill-color: transparent;
538
- color: transparent;
539
- 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
+ }
540
626
  }
541
627
 
542
- @keyframes blade-shimmer {
628
+ @keyframes blade-turn-nav-flash {
543
629
  from {
544
- background-position: 200% 0;
630
+ background-color: hsl(var(--accent) / 0.75);
545
631
  }
546
632
  to {
547
- background-position: -200% 0;
633
+ background-color: transparent;
548
634
  }
549
635
  }