@blade-hq/agent-react 1.2.1-beta.12 → 1.2.1-beta.14
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.
- package/README.md +12 -6
- package/dist/components/ChatView.d.ts +2 -0
- package/dist/components/ToolCallBlock.d.ts +17 -0
- package/dist/embed/blade-chat-element.d.ts +1 -1
- package/dist/index.js +39 -11
- package/dist/index.js.map +1 -1
- package/dist/style.full.css +675 -0
- package/package.json +4 -3
- package/public-api.md +1 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ pnpm add @blade-hq/agent-client @blade-hq/agent-react
|
|
|
13
13
|
```tsx
|
|
14
14
|
import { BladeClient } from "@blade-hq/agent-client"
|
|
15
15
|
import { BladeProvider, ChatView } from "@blade-hq/agent-react"
|
|
16
|
-
import "@blade-hq/agent-react/style.css"
|
|
16
|
+
import "@blade-hq/agent-react/style.full.css"
|
|
17
17
|
|
|
18
18
|
const client = new BladeClient({ baseUrl: "https://blade.example.com" })
|
|
19
19
|
|
|
@@ -88,7 +88,11 @@ await session?.send("你好")
|
|
|
88
88
|
|
|
89
89
|
全部 props 见 `ChatViewProps`。样式说明:
|
|
90
90
|
|
|
91
|
-
-
|
|
91
|
+
- 必须引入一份样式,按宿主有没有 Tailwind 二选一:
|
|
92
|
+
- **`style.full.css`**(默认选它):布局兜底 + 编译好的 Tailwind 产物,自包含,宿主没装 Tailwind 也是完整视觉。
|
|
93
|
+
- **`style.css`**:只有 `.blade-chat-*` 布局兜底。宿主自己装了 Tailwind、且 `content` 配了扫描 `node_modules/@blade-hq/agent-react` 时用它,可以少一份重复 CSS。选错不会报错,只是工具块、思考块内部会退化成没有布局的裸 HTML。
|
|
94
|
+
- 两份都不含 Tailwind preflight 这类全局 reset,引入后不会影响宿主页面自己的排版;组件需要的那点重置限定在 `.blade-chat` 子树内(见 `src/scoped-preflight.css`)。Shadow DOM 版(`<blade-chat>`)不受此限,它注入的是带 preflight 的完整产物。
|
|
95
|
+
- 颜色走 CSS 变量,可整体换肤;深色页面要在根元素上写 `data-theme="dark"`,否则拿到的是默认的浅色变量。
|
|
92
96
|
- `renderers.toolCall` 返回 `null` 时回落到默认工具卡片。
|
|
93
97
|
- **内边距按容器宽度伸缩**,嵌进窄侧栏(300–400px)时会自动收窄,不看浏览器视口。代价是聊天容器需要有确定宽度——放在 flex/grid 里被拉伸,或显式给宽度都可以;放进 `width: fit-content`、`inline-flex` 这类由内容决定宽度的父容器会塌缩。
|
|
94
98
|
|
|
@@ -118,17 +122,19 @@ SDK 的 ChatView / `<blade-chat>` 不内置模型选择器(默认用后端配
|
|
|
118
122
|
|
|
119
123
|
## 样式定制
|
|
120
124
|
|
|
121
|
-
|
|
125
|
+
默认是浅色主题。四个层次,按需选用:
|
|
122
126
|
|
|
123
|
-
1.
|
|
127
|
+
1. **深浅主题**:`<ChatView theme="dark" />` / `<blade-chat theme="dark">` 切内置深色,不传或 `"light"` 为浅色。宿主已经在祖先元素上写了 `data-theme="dark"` 时不用传,`ChatView` 会跟着变。`<blade-chat>` 渲染在 Shadow DOM 内,页面上的 `data-theme` 对它无效,只认 `theme` 属性。
|
|
128
|
+
|
|
129
|
+
2. **CSS 变量换肤**(React 与 `<blade-chat>` 通用):所有颜色走 `--primary`、`--background`、`--muted` 等变量(完整清单见 style.css 开头)。页面直接覆盖即可穿透 Shadow DOM:
|
|
124
130
|
|
|
125
131
|
```css
|
|
126
132
|
blade-chat { --primary: 262 83% 58%; height: 640px; }
|
|
127
133
|
```
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
3. **classNames props**(React):`ChatView` 的 `classNames` 把自定义 class(含你自己构建的 Tailwind 工具类)挂到根节点、消息区、输入区等关键节点上,见 `ChatViewClassNames`。
|
|
130
136
|
|
|
131
|
-
|
|
137
|
+
4. **内嵌 `<style>`(纯 HTML / Vue)**:`<blade-chat>` 的直接 `<style>` 子元素会被注入 Shadow DOM、排在内置样式之后,可用 `.blade-chat-*` 类名做任意深度定制:
|
|
132
138
|
|
|
133
139
|
```html
|
|
134
140
|
<blade-chat base-url="...">
|
|
@@ -10,6 +10,23 @@ interface Props {
|
|
|
10
10
|
sessionStatus?: string;
|
|
11
11
|
renderer?: ToolCallRenderer;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* 判断提问卡片该等待作答还是展示成已作答,只认工具状态。
|
|
15
|
+
* 与 apps/web 的 ToolCallBlock 里同名函数保持一致:两处算出不同结果时,
|
|
16
|
+
* 同一份会话在内置页面能点、在 SDK 里点不动。
|
|
17
|
+
*
|
|
18
|
+
* 不要再拿 sessionStatus 兜底。历史投影算错时用 waiting_for_input 补救,
|
|
19
|
+
* 会同时把已经回答过的旧提问重新变成可作答(历史加载不会带回 answerData),
|
|
20
|
+
* 而且会盖住投影层的错误——这个 bug 就是这么藏住的。
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveAskQuestionState({ toolStatus, hasAnswerData, fallbackAnswered, }: {
|
|
23
|
+
toolStatus: ToolCallInfo["status"];
|
|
24
|
+
hasAnswerData: boolean;
|
|
25
|
+
fallbackAnswered?: boolean;
|
|
26
|
+
}): {
|
|
27
|
+
awaitingAnswer: boolean;
|
|
28
|
+
answered: boolean;
|
|
29
|
+
};
|
|
13
30
|
/**
|
|
14
31
|
* 极简工具调用块:状态 + 展示名一行,点开后是参数/结果的 <pre> 折叠展示。
|
|
15
32
|
* AskUserQuestion 特殊处理为提问选项卡片。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AgentSession } from "@blade-hq/agent-client";
|
|
2
2
|
/**
|
|
3
|
-
* `<blade-chat base-url="https://..." session-id="可选">`
|
|
3
|
+
* `<blade-chat base-url="https://..." session-id="可选" theme="light|dark">`
|
|
4
4
|
*
|
|
5
5
|
* 纯静态页面的聊天入口:Shadow DOM 内渲染完整 ChatView,
|
|
6
6
|
* 未登录时组件自带登录引导,不传 session-id 自动建会话。
|
package/dist/index.js
CHANGED
|
@@ -1690,6 +1690,17 @@ function normalizeOptionItem(value) {
|
|
|
1690
1690
|
|
|
1691
1691
|
// src/components/ToolCallBlock.tsx
|
|
1692
1692
|
import { Fragment, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1693
|
+
function resolveAskQuestionState({
|
|
1694
|
+
toolStatus,
|
|
1695
|
+
hasAnswerData,
|
|
1696
|
+
fallbackAnswered
|
|
1697
|
+
}) {
|
|
1698
|
+
const awaitingAnswer = !hasAnswerData && toolStatus === "awaiting_answer";
|
|
1699
|
+
return {
|
|
1700
|
+
awaitingAnswer,
|
|
1701
|
+
answered: hasAnswerData || !awaitingAnswer && (Boolean(fallbackAnswered) || toolStatus === "done" || toolStatus === "cancelled" || toolStatus === "error")
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1693
1704
|
function ToolCallBlock({
|
|
1694
1705
|
toolCall,
|
|
1695
1706
|
onAnswer,
|
|
@@ -1708,13 +1719,18 @@ function ToolCallBlock({
|
|
|
1708
1719
|
}
|
|
1709
1720
|
if (normalizedName === "AskUserQuestion") {
|
|
1710
1721
|
const askData = parseAskUserQuestion(typeof toolCall.result === "string" ? toolCall.result : null) ?? parseAskUserQuestion(buildAskUserPayload(toolCall.arguments));
|
|
1711
|
-
const
|
|
1722
|
+
const questionState = resolveAskQuestionState({
|
|
1723
|
+
toolStatus: toolCall.status,
|
|
1724
|
+
hasAnswerData: Boolean(answerData),
|
|
1725
|
+
fallbackAnswered: answered
|
|
1726
|
+
});
|
|
1727
|
+
const canAnswer = questionState.awaitingAnswer && Boolean(onAnswer);
|
|
1712
1728
|
if (askData) {
|
|
1713
1729
|
return /* @__PURE__ */ jsx9(
|
|
1714
1730
|
AskUserQuestionBlock,
|
|
1715
1731
|
{
|
|
1716
1732
|
data: askData,
|
|
1717
|
-
answered:
|
|
1733
|
+
answered: questionState.answered,
|
|
1718
1734
|
toolCallId: toolCall.id,
|
|
1719
1735
|
sessionStatus: sessionStatus ?? "",
|
|
1720
1736
|
answerData,
|
|
@@ -2207,6 +2223,9 @@ function PlanningDivider({ kind }) {
|
|
|
2207
2223
|
|
|
2208
2224
|
// src/components/ChatView.tsx
|
|
2209
2225
|
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2226
|
+
function themeAttr(theme) {
|
|
2227
|
+
return theme === "dark" ? "dark" : void 0;
|
|
2228
|
+
}
|
|
2210
2229
|
function isUnauthorizedError(error) {
|
|
2211
2230
|
return error instanceof BladeApiError && error.status === 401;
|
|
2212
2231
|
}
|
|
@@ -2247,16 +2266,23 @@ function ChatView(props) {
|
|
|
2247
2266
|
const [attempt, setAttempt] = useState9(0);
|
|
2248
2267
|
const [needLogin, setNeedLogin] = useState9(() => !client.hasToken());
|
|
2249
2268
|
if (needLogin) {
|
|
2250
|
-
return /* @__PURE__ */ jsx14(
|
|
2251
|
-
|
|
2269
|
+
return /* @__PURE__ */ jsx14(
|
|
2270
|
+
"div",
|
|
2252
2271
|
{
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2272
|
+
"data-theme": themeAttr(props.theme),
|
|
2273
|
+
className: cn("blade-chat flex min-h-0 flex-1 flex-col", props.classNames?.root),
|
|
2274
|
+
children: /* @__PURE__ */ jsx14(
|
|
2275
|
+
LoginCard,
|
|
2276
|
+
{
|
|
2277
|
+
client,
|
|
2278
|
+
onLoggedIn: () => {
|
|
2279
|
+
setNeedLogin(false);
|
|
2280
|
+
setAttempt((value) => value + 1);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
)
|
|
2258
2284
|
}
|
|
2259
|
-
)
|
|
2285
|
+
);
|
|
2260
2286
|
}
|
|
2261
2287
|
return /* @__PURE__ */ jsx14(ChatSessionView, { ...props, onUnauthorized: () => setNeedLogin(true) }, attempt);
|
|
2262
2288
|
}
|
|
@@ -2270,6 +2296,7 @@ function ChatSessionView({
|
|
|
2270
2296
|
renderers,
|
|
2271
2297
|
slots,
|
|
2272
2298
|
placeholder,
|
|
2299
|
+
theme,
|
|
2273
2300
|
onUnauthorized
|
|
2274
2301
|
}) {
|
|
2275
2302
|
const { session, state, error } = useAgentSession(sessionId, {
|
|
@@ -2328,6 +2355,7 @@ ${content}`);
|
|
|
2328
2355
|
return /* @__PURE__ */ jsxs12(
|
|
2329
2356
|
"div",
|
|
2330
2357
|
{
|
|
2358
|
+
"data-theme": themeAttr(theme),
|
|
2331
2359
|
className: cn(
|
|
2332
2360
|
"blade-chat flex min-h-0 flex-1 flex-col overflow-hidden",
|
|
2333
2361
|
classNames?.root
|
|
@@ -2340,7 +2368,7 @@ ${content}`);
|
|
|
2340
2368
|
className: classNames?.banner
|
|
2341
2369
|
}
|
|
2342
2370
|
),
|
|
2343
|
-
errorMessage && /* @__PURE__ */ jsxs12("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b
|
|
2371
|
+
errorMessage && /* @__PURE__ */ jsxs12("div", { className: "blade-chat-error-bar flex items-start gap-2 border-b px-4 py-3 text-sm", children: [
|
|
2344
2372
|
/* @__PURE__ */ jsx14(CircleAlert, { size: 16, className: "mt-0.5 shrink-0" }),
|
|
2345
2373
|
/* @__PURE__ */ jsx14("span", { children: errorMessage })
|
|
2346
2374
|
] }),
|