@sophonsai/agent-embed-react 0.0.0-stage → 0.1.0

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 CHANGED
@@ -1,3 +1,310 @@
1
- # Temporary Holding Version
1
+ # @sophonsai/agent-embed-react
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ## 应用接入
4
+
5
+ 传入服务地址 `endpoint` 和 Agent 的应用接入 `token`;可选 `baseUrl` 默认是 `/e/v1/embed`。
6
+ 最终 iframe 地址是 `endpoint + baseUrl + '/' + encodeURIComponent(token)`。
7
+
8
+ ```tsx
9
+ <AgentEmbed endpoint='https://agent.example.com' token='应用接入Token' style={{ height: 600 }} />
10
+ ```
11
+
12
+ 不再接收完整 `url`、旧 `initConfig`、Agent ID 或平台用户凭证。
13
+ `threadId`、外观配置、实例命令和事件回调继续支持。
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ # npm
19
+ npm install @sophonsai/agent-embed-react react
20
+
21
+ # pnpm
22
+ pnpm add @sophonsai/agent-embed-react react
23
+
24
+ # yarn
25
+ yarn add @sophonsai/agent-embed-react react
26
+
27
+ # bun
28
+ bun add @sophonsai/agent-embed-react react
29
+ ```
30
+
31
+ ## 本地 Playground
32
+
33
+ 在仓库根目录运行:
34
+
35
+ ```bash
36
+ pnpm dev:agent-embed
37
+ ```
38
+
39
+ 命令会复用或启动 Extension 开发服务(3023),启动 Agent Embed Playground(4171),并自动打开浏览器。Playground 支持服务地址、嵌入路径、应用接入 Token、appearance、实例命令和 bridge 生命周期日志。
40
+
41
+ ## 快速开始
42
+
43
+ ```tsx
44
+ import { useState } from 'react'
45
+ import { AgentEmbed, type AgentAppearance, type AgentEmbedInstance } from '@sophonsai/agent-embed-react'
46
+
47
+ export function ChatPage() {
48
+ const [agent, setAgent] = useState<AgentEmbedInstance | null>(null)
49
+ const [appearance, setAppearance] = useState<AgentAppearance>({
50
+ theme: { preset: 'glacier-blue' },
51
+ fontSize: 16,
52
+ header: {
53
+ historyButton: true,
54
+ newConversationButton: true,
55
+ themeSettingButton: false,
56
+ },
57
+ })
58
+
59
+ return (
60
+ <div style={{ height: 640 }}>
61
+ <AgentEmbed
62
+ className='agent-embed'
63
+ endpoint='https://agent.example.com'
64
+ token='应用接入Token'
65
+ appearance={appearance}
66
+ onThemeChange={(theme) => setAppearance((current) => ({ ...current, theme }))}
67
+ onFontSizeChange={(fontSize) => setAppearance((current) => ({ ...current, fontSize }))}
68
+ onInitError={({ code, message }) => console.error('初始化失败', code, message)}
69
+ onReady={setAgent}
70
+ onSettled={(result) => console.log('会话结束', result)}
71
+ />
72
+
73
+ <button type='button' disabled={!agent} onClick={() => agent?.sendMessage({ text: '你好' })}>
74
+ 发送消息
75
+ </button>
76
+ </div>
77
+ )
78
+ }
79
+ ```
80
+
81
+ ## 组件 Props
82
+
83
+ `AgentEmbedProps` 继承 SDK 的 `CreateAgentEmbedOptions`,并增加 React 容器属性:
84
+
85
+ | Prop | 类型 | 必填 | 说明 |
86
+ | ------------------- | ----------------------- | ---- | ------------------------------------- |
87
+ | `endpoint` | `string` | 是 | 服务地址 |
88
+ | `token` | `string` | 是 | Agent 应用接入凭证 |
89
+ | `baseUrl` | `string` | 否 | 嵌入路径,默认 `/e/v1/embed` |
90
+ | `threadId` | `string` | 否 | 初始会话 ID |
91
+ | `runtime_variables` | `AgentRuntimeVariables` | 否 | 新会话绑定的运行时变量,值必须为 JSON |
92
+ | `appearance` | `AgentAppearance` | 否 | 主题、字号和头部按钮配置 |
93
+ | `onReady` | `(instance) => void` | 否 | 面板可用后返回操作实例 |
94
+ | `onThemeChange` | `(theme) => void` | 否 | 用户在面板内切换主题时调用 |
95
+ | `onFontSizeChange` | `(fontSize) => void` | 否 | 用户在面板内调整字号时调用 |
96
+ | `onInitError` | `(failure) => void` | 否 | 初始化失败 |
97
+ | `className` | `string` | 否 | 应用于根 `div` |
98
+ | `style` | `CSSProperties` | 否 | 应用于根 `div`,可用于设置高度 |
99
+
100
+ 此外可以传入 `AgentEvents` 中定义的所有会话事件,例如 `onToolCallStarted`、`onSuccess` 和 `onSettled`。
101
+
102
+ ## 初始化配置
103
+
104
+ ```tsx
105
+ <AgentEmbed
106
+ endpoint='https://agent.example.com'
107
+ token='应用接入Token'
108
+ baseUrl='/e/v1/embed'
109
+ threadId='可选初始会话ID'
110
+ />
111
+ ```
112
+
113
+ `baseUrl` 可以省略。初始化配置与回调只在挂载时读取;需要切换服务、token、会话或回调时,修改组件 `key` 重新挂载:
114
+
115
+ ```tsx
116
+ <AgentEmbed key={`${endpoint}:${token}:${threadId}`} endpoint={endpoint} token={token} threadId={threadId} />
117
+ ```
118
+
119
+ ## 外观与受控状态
120
+
121
+ `appearance` 的三个部分支持在挂载后动态更新:
122
+
123
+ ```ts
124
+ type AgentAppearance = {
125
+ theme?: { preset: AgentEmbedThemePreset }
126
+ fontSize?: number
127
+ header?: {
128
+ historyButton?: boolean
129
+ newConversationButton?: boolean
130
+ themeSettingButton?: boolean
131
+ }
132
+ }
133
+ ```
134
+
135
+ `header.themeSettingButton` 默认值为 `false`,只有显式设置为 `true` 时才显示主题设置按钮。
136
+
137
+ 可用主题:
138
+
139
+ 可以使用 `AgentEmbedThemePreset` 枚举式常量设置主题:
140
+
141
+ ```tsx
142
+ import { AgentEmbedThemePreset } from '@sophonsai/agent-embed-react'
143
+
144
+ const appearance = {
145
+ theme: { preset: AgentEmbedThemePreset.GlacierBlue },
146
+ }
147
+ ```
148
+
149
+ | 主题名 | preset | 类型 |
150
+ | ------ | ----------------- | -------- |
151
+ | 曜石紫 | `obsidian-violet` | 深色主题 |
152
+ | 石墨青 | `graphite-cyan` | 深色主题 |
153
+ | 午夜靛 | `midnight-indigo` | 深色主题 |
154
+ | 深海蓝 | `deep-ocean` | 深色主题 |
155
+ | 森野绿 | `forest-emerald` | 深色主题 |
156
+ | 影棚白 | `studio-white` | 浅色主题 |
157
+ | 珍珠紫 | `pearl-violet` | 浅色主题 |
158
+ | 冰川蓝 | `glacier-blue` | 浅色主题 |
159
+ | 暖杏色 | `warm-cream` | 浅色主题 |
160
+ | 薰衣草 | `lavender-mist` | 浅色主题 |
161
+
162
+ 当 `appearance.theme` 或 `appearance.fontSize` 有值时,对应字段属于受控状态。面板内发生变化后,应在回调里更新 React state,否则下一次渲染仍会使用旧值:
163
+
164
+ ```tsx
165
+ onThemeChange={(theme) =>
166
+ setAppearance((current) => ({ ...current, theme }))
167
+ }
168
+ onFontSizeChange={(fontSize) =>
169
+ setAppearance((current) => ({ ...current, fontSize }))
170
+ }
171
+ ```
172
+
173
+ `appearance.header` 的变化会自动同步到面板。把某个字段设为 `false` 会隐藏对应按钮;把整个配置改为 `undefined` 会撤销宿主控制。
174
+
175
+ ## 获取和使用实例
176
+
177
+ 面板初始化完成后,`onReady` 会收到 `AgentEmbedInstance`。在此之前,获取不到 `AgentEmbedInstance` 实例对象,无法操作面板执行行为。
178
+
179
+ ```tsx
180
+ const [agent, setAgent] = useState<AgentEmbedInstance | null>(null)
181
+
182
+ <AgentEmbed endpoint={endpoint} token={token} onReady={setAgent} />
183
+
184
+ agent?.addInputQuery({
185
+ text: '请分析这段内容',
186
+ context: selectedText,
187
+ })
188
+ agent?.focusInput()
189
+ ```
190
+
191
+ 实例方法:
192
+
193
+ | 方法 | 说明 |
194
+ | ----------------------------------- | ------------------------------------------- |
195
+ | `sendMessage({ text })` | 立即提交消息;生成期间调用会使用 steer 行为 |
196
+ | `addInputQuery({ text, context? })` | 写入输入框和可选上下文,不立即提交 |
197
+ | `abortResponse()` | 终止当前回复 |
198
+ | `openHistoryPanel()` | 打开历史会话面板 |
199
+ | `openThemeSettingsPanel()` | 打开主题设置面板 |
200
+ | `newConversation()` | 新建会话 |
201
+ | `focusInput()` | 聚焦输入框 |
202
+ | `scrollToBottom()` | 滚动到底部 |
203
+
204
+ 组件卸载后内部 client 会被销毁,之前保存的 instance 随之失效。建议在组件切换或卸载时同步清空外部保存的 instance。
205
+
206
+ ## 错误处理
207
+
208
+ 初始化失败通过结构化回调报告:
209
+
210
+ ```tsx
211
+ <AgentEmbed
212
+ endpoint={endpoint}
213
+ token={token}
214
+ onInitError={({ code, message }) => {
215
+ // code: invalid_token | forbidden | agent_not_found | request_failed | frame_load_failed
216
+ console.error(code, message)
217
+ }}
218
+ />
219
+ ```
220
+
221
+ 面板初始化完成后才会调用 `onReady`。发生上述初始化失败时不会调用 `onReady`。
222
+
223
+ ## 会话事件
224
+
225
+ 下面是几个常见用法:
226
+
227
+ ```tsx
228
+ <AgentEmbed
229
+ endpoint={endpoint}
230
+ token={token}
231
+ onSessionContextReady={(context) => {
232
+ console.log('会话 ID', context.threadId)
233
+ }}
234
+ onToolCallFailed={(event) => {
235
+ console.error(event.toolName, event.error)
236
+ }}
237
+ onSettled={(result) => {
238
+ console.log(result.status)
239
+ }}
240
+ />
241
+ ```
242
+
243
+ 全部运行事件回调如下:
244
+
245
+ | 回调 | 参数 | 说明 |
246
+ | --------------------------- | --------------------------------------------------------------- | ------------------------------------------- |
247
+ | `onConcurrentSend` | 无 | 回复生成期间再次发送消息 |
248
+ | `onEvent` | `AgentServerEvent` | 接收原始服务端事件 |
249
+ | `onSessionContextReady` | `AgentSessionContext` | 会话上下文可用,包含 `threadId` 和 `turnId` |
250
+ | `onToolCallCreated` | `AgentToolCallCreatedEvent`, `AgentSessionEventContext` | 创建一个工具调用 |
251
+ | `onToolApprovalRequested` | `AgentToolApprovalRequestedEvent`, `AgentSessionEventContext` | 一组工具调用需要确认 |
252
+ | `onToolCallStarted` | `AgentToolCallStartedEvent`, `AgentSessionEventContext` | 工具调用开始 |
253
+ | `onToolCallProgressUpdated` | `AgentToolCallProgressUpdatedEvent`, `AgentSessionEventContext` | 工具调用进度更新 |
254
+ | `onToolCallBackgrounded` | `AgentToolCallBackgroundedEvent`, `AgentSessionEventContext` | 工具调用转为后台执行 |
255
+ | `onToolCallSucceeded` | `AgentToolCallSucceededEvent`, `AgentSessionEventContext` | 工具调用成功 |
256
+ | `onToolCallFailed` | `AgentToolCallFailedEvent`, `AgentSessionEventContext` | 工具调用失败 |
257
+ | `onToolCallCancelled` | `AgentToolCallCancelledEvent`, `AgentSessionEventContext` | 工具调用取消 |
258
+ | `onClientActionRequest` | `AgentClientActionRequestEvent`, `AgentSessionEventContext` | 智能体请求宿主执行客户端动作 |
259
+ | `onUserFeedbackConsumed` | `AgentUserFeedbackConsumedEvent`, `AgentSessionEventContext` | 用户反馈已被处理 |
260
+ | `onUserSteerConsumed` | `AgentUserSteerConsumedEvent`, `AgentSessionEventContext` | 用户 steer 指令已被处理 |
261
+ | `onUserSteerCancelled` | `AgentUserSteerCancelledEvent`, `AgentSessionRunEventContext` | 用户 steer 指令已取消 |
262
+ | `onUserInputSubmitted` | `AgentUserInputSubmittedEvent`, `AgentSessionEventContext` | 用户输入已提交 |
263
+ | `onSessionTurnStarted` | `AgentSessionTurnStartedEvent`, `AgentSessionEventContext` | Turn 开始执行 |
264
+ | `onSessionTurnSucceeded` | `AgentSessionTurnSucceededEvent`, `AgentSessionEventContext` | Turn 执行成功 |
265
+ | `onSessionTurnFailed` | `AgentSessionTurnFailedEvent`, `AgentSessionEventContext` | Turn 执行失败 |
266
+ | `onSessionTurnCancelled` | `AgentSessionTurnCancelledEvent`, `AgentSessionEventContext` | Turn 已取消 |
267
+ | `onSessionRunStarted` | `AgentSessionRunStartedEvent`, `AgentSessionRunEventContext` | Run 已开始 |
268
+ | `onSessionRunIdled` | `AgentSessionRunIdledEvent`, `AgentSessionRunEventContext` | Run 已空闲 |
269
+ | `onSessionRunEnded` | `AgentSessionRunEndedEvent`, `AgentSessionRunEventContext` | Run 已结束 |
270
+ | `onSystemMessageConsumed` | `AgentSystemMessageConsumedEvent`, `AgentSessionEventContext` | 系统消息已被处理 |
271
+ | `onSuccess` | `AgentSessionSuccess` | 本轮会话成功结束 |
272
+ | `onError` | `AgentSessionFailure` | 本轮会话失败 |
273
+ | `onCancelled` | `AgentSessionCancelled` | 本轮会话取消 |
274
+ | `onSettled` | `AgentSessionSettlement` | 本轮会话以任意状态结束 |
275
+
276
+ 所有相关类型都由本包重导出。
277
+
278
+ ## 布局说明
279
+
280
+ 组件根节点默认样式为:
281
+
282
+ ```css
283
+ width: 100%;
284
+ height: 100%;
285
+ min-height: 0;
286
+ ```
287
+
288
+ 因此父容器必须提供高度:
289
+
290
+ ```tsx
291
+ <main style={{ height: 'calc(100vh - 64px)' }}>
292
+ <AgentEmbed endpoint={endpoint} token={token} />
293
+ </main>
294
+ ```
295
+
296
+ 也可以直接通过 `style` 设置最小高度:
297
+
298
+ ```tsx
299
+ <AgentEmbed style={{ minHeight: 480 }} endpoint={endpoint} token={token} />
300
+ ```
301
+
302
+ ### 运行时变量
303
+
304
+ `runtime_variables` 是可选的 JSON 对象,名称须与智能体声明的运行时变量一致。仅创建新会话时发送,已有会话沿用已绑定的变量;同一面板新建会话时复用初始化值。参数在初始化时生成快照,改变值需要重建面板。数据通过 iframe 消息传递,不写入接入 URL。
305
+
306
+ ```tsx
307
+ <AgentEmbed endpoint={endpoint} token={token} runtime_variables={{ customer_id: 'customer-1' }} />
308
+ ```
309
+
310
+ 初始化错误使用 `onInitError`(Vue: `@init-error`),包含 `code` 和 `message`,同次初始化只触发一次。失败时 `whenReady()` reject,聊天运行错误继续通过 `onError`(Vue: `@error`)报告。
@@ -0,0 +1,15 @@
1
+ import { CSSProperties } from "react";
2
+ import { CreateAgentEmbedOptions } from "@sophonsai/agent-embed-sdk";
3
+ export * from "@sophonsai/agent-embed-sdk";
4
+ //#region src/index.d.ts
5
+ type AgentEmbedProps = CreateAgentEmbedOptions & {
6
+ className?: string;
7
+ style?: CSSProperties;
8
+ };
9
+ declare const AgentEmbed: {
10
+ (props: AgentEmbedProps): import("react").JSX.Element;
11
+ displayName: string;
12
+ };
13
+ //#endregion
14
+ export { AgentEmbed, AgentEmbed as default, AgentEmbedProps };
15
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.tsx"],"mappings":";;;;KAKY,kBAAkB;EAC5B;EACA,QAAQ;;cAGG;GAAqB,OAAA,kCAAe,IAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,45 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { createAgentEmbed } from "@sophonsai/agent-embed-sdk";
3
+ import { jsx } from "react/jsx-runtime";
4
+ export * from "@sophonsai/agent-embed-sdk";
5
+ //#region src/index.tsx
6
+ const AgentEmbed = (props) => {
7
+ const hostRef = useRef(null);
8
+ const clientRef = useRef(null);
9
+ useEffect(() => {
10
+ const host = hostRef.current;
11
+ if (!host) return;
12
+ const { className: _className, style: _style, ...options } = props;
13
+ const client = createAgentEmbed(options);
14
+ clientRef.current = client;
15
+ client.mount(host).catch(() => void 0);
16
+ return () => {
17
+ client.destroy();
18
+ clientRef.current = null;
19
+ };
20
+ }, []);
21
+ useEffect(() => {
22
+ clientRef.current?.updateTheme(props.appearance?.theme);
23
+ }, [props.appearance?.theme]);
24
+ useEffect(() => {
25
+ clientRef.current?.updateFontSize(props.appearance?.fontSize);
26
+ }, [props.appearance?.fontSize]);
27
+ useEffect(() => {
28
+ clientRef.current?.updateHeader(props.appearance?.header);
29
+ }, [props.appearance?.header]);
30
+ return /* @__PURE__ */ jsx("div", {
31
+ ref: hostRef,
32
+ className: props.className,
33
+ style: {
34
+ width: "100%",
35
+ height: "100%",
36
+ minHeight: 0,
37
+ ...props.style
38
+ }
39
+ });
40
+ };
41
+ AgentEmbed.displayName = "AgentEmbed";
42
+ //#endregion
43
+ export { AgentEmbed, AgentEmbed as default };
44
+
45
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.tsx"],"sourcesContent":["import { useEffect, useRef, type CSSProperties } from 'react'\nimport { createAgentEmbed, type AgentEmbedClient, type CreateAgentEmbedOptions } from '@sophonsai/agent-embed-sdk'\n\nexport * from '@sophonsai/agent-embed-sdk'\n\nexport type AgentEmbedProps = CreateAgentEmbedOptions & {\n className?: string\n style?: CSSProperties\n}\n\nexport const AgentEmbed = (props: AgentEmbedProps) => {\n const hostRef = useRef<HTMLDivElement>(null)\n const clientRef = useRef<AgentEmbedClient>(null)\n\n useEffect(() => {\n const host = hostRef.current\n if (!host) {\n return\n }\n const { className: _className, style: _style, ...options } = props\n const client = createAgentEmbed(options)\n clientRef.current = client\n void client.mount(host).catch(() => undefined)\n return () => {\n client.destroy()\n clientRef.current = null\n }\n // Init values and callbacks are snapshotted when the client is created.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n useEffect(() => {\n clientRef.current?.updateTheme(props.appearance?.theme)\n }, [props.appearance?.theme])\n\n useEffect(() => {\n clientRef.current?.updateFontSize(props.appearance?.fontSize)\n }, [props.appearance?.fontSize])\n\n useEffect(() => {\n clientRef.current?.updateHeader(props.appearance?.header)\n }, [props.appearance?.header])\n\n return (\n <div\n ref={hostRef}\n className={props.className}\n style={{ width: '100%', height: '100%', minHeight: 0, ...props.style } as CSSProperties}\n />\n )\n}\n\nAgentEmbed.displayName = 'AgentEmbed'\n\nexport default AgentEmbed\n"],"mappings":";;;;;AAUA,MAAa,cAAc,UAA2B;CACpD,MAAM,UAAU,OAAuB,IAAI;CAC3C,MAAM,YAAY,OAAyB,IAAI;CAE/C,gBAAgB;EACd,MAAM,OAAO,QAAQ;EACrB,IAAI,CAAC,MACH;EAEF,MAAM,EAAE,WAAW,YAAY,OAAO,QAAQ,GAAG,YAAY;EAC7D,MAAM,SAAS,iBAAiB,OAAO;EACvC,UAAU,UAAU;EACpB,OAAY,MAAM,IAAI,CAAC,CAAC,YAAY,KAAA,CAAS;EAC7C,aAAa;GACX,OAAO,QAAQ;GACf,UAAU,UAAU;EACtB;CAGF,GAAG,CAAC,CAAC;CAEL,gBAAgB;EACd,UAAU,SAAS,YAAY,MAAM,YAAY,KAAK;CACxD,GAAG,CAAC,MAAM,YAAY,KAAK,CAAC;CAE5B,gBAAgB;EACd,UAAU,SAAS,eAAe,MAAM,YAAY,QAAQ;CAC9D,GAAG,CAAC,MAAM,YAAY,QAAQ,CAAC;CAE/B,gBAAgB;EACd,UAAU,SAAS,aAAa,MAAM,YAAY,MAAM;CAC1D,GAAG,CAAC,MAAM,YAAY,MAAM,CAAC;CAE7B,OACE,oBAAC,OAAD;EACE,KAAK;EACL,WAAW,MAAM;EACjB,OAAO;GAAE,OAAO;GAAQ,QAAQ;GAAQ,WAAW;GAAG,GAAG,MAAM;EAAM;CACtE,CAAA;AAEL;AAEA,WAAW,cAAc"}
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@sophonsai/agent-embed-react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "React adapter for Sophon Agent Embed.",
5
+ "type": "module",
6
+ "keywords": [
7
+ "sophonsai",
8
+ "agent",
9
+ "agent-embed",
10
+ "react"
11
+ ],
12
+ "publishConfig": {
13
+ "registry": "https://registry.npmjs.org/",
14
+ "access": "public"
15
+ },
16
+ "types": "./dist/index.d.mts",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.mts",
21
+ "import": "./dist/index.mjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "dependencies": {
28
+ "@sophonsai/agent-embed-sdk": "0.1.0"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=18"
32
+ },
33
+ "devDependencies": {
34
+ "@vitejs/plugin-react": "^6.0.2",
35
+ "jsdom": "^27.4.0",
36
+ "react": "^19.2.6",
37
+ "react-dom": "^19.2.6",
38
+ "tsdown": "^0.22.14"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown src/index.tsx --format esm --dts",
42
+ "typecheck": "tsc -p tsconfig.json --noEmit",
43
+ "test": "vitest run --config vitest.config.ts",
44
+ "example": "vite --config example/vite.config.ts"
45
+ }
6
46
  }