@_daniel_jiang/host-sdk 1.0.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 ADDED
@@ -0,0 +1,163 @@
1
+ # @agent-platform/host-sdk
2
+
3
+ Agent 平台宿主嵌入 SDK(iframe 模式)。UI 由 Agent `/host/hub`、`/host/widget` 渲染;宿主只需 `init` + `registerPage`。
4
+
5
+ 表单提交与附件上传均在 **Agent Widget iframe 内**完成:用户点「开始」后,Widget 以 **multipart** 一次调用 `POST /api/v1/host/scenarios/{workflow}/run`(表单 JSON 字段 + 可选 `attachment_files`)。**不再**使用独立的 `POST /sessions/{id}/files`。
6
+
7
+ ## 安装
8
+
9
+ 宿主工程(EDC `boot-ui` / 专病库 `imedway-rdr-web`)使用**已拷贝到本仓的 vendor 包**,不依赖 agent-platform 源码树:
10
+
11
+ ```bash
12
+ # 依赖已指向本地副本,例如:
13
+ # "@agent-platform/host-sdk": "file:./vendor/agent-platform-host-sdk"
14
+ npm install
15
+ ```
16
+
17
+ 在 **agent-platform** 更新 SDK 后,于本目录执行同步(会先 `pnpm run build:sdk`):
18
+
19
+ ```bash
20
+ bash web/packages/host-sdk/sync-to-hosts.sh
21
+ # 然后在对应宿主工程重新 npm install
22
+ ```
23
+
24
+ monorepo 内临时联调仍可指向源包:
25
+
26
+ ```bash
27
+ npm install file:../../../web/packages/host-sdk
28
+ ```
29
+
30
+ 安装前需已生成 `dist/`(`web/` 下执行 `pnpm run build:sdk`)。
31
+
32
+ ## 用法
33
+
34
+ ```javascript
35
+ import { AgentHost } from '@agent-platform/host-sdk'
36
+
37
+ await AgentHost.init({
38
+ host: 'edc',
39
+ // 推荐:BFF bootstrap 返回 agent_token + agent_web_origin,无需前端环境变量
40
+ bootstrap: () => fetch('/boot-admin/agent/bootstrap?hosts=edc', { credentials: 'include' }).then((r) => r.json()),
41
+ // 可选:打开需 establish 的 Widget 前强制刷新会话
42
+ // prepareOpenWidget: () => bootstrapAgentSession(true),
43
+ })
44
+
45
+ AgentHost.registerPage(
46
+ 'edc_patient_list',
47
+ [{
48
+ id: 'form_fill',
49
+ workflow: 'edc_form_fill',
50
+ label: 'AI 填表',
51
+ host: 'edc',
52
+ prefilled: () => ({
53
+ sub_project_id: currentSubProjectId,
54
+ project_id: currentProjectId,
55
+ target: buildTargetFromEdcSelection(selectedForm),
56
+ }),
57
+ enabled: () => AgentHost.canOpen('edc_form_fill', { prefilled: { ... } }),
58
+ hostContext: { refresh: 'patient_list' },
59
+ }],
60
+ {
61
+ // 任务 success 且 hostContext.refresh 匹配时调用;unregisterPage 后自动失效
62
+ refreshKey: 'patient_list',
63
+ onRefresh: () => refreshPatientList(),
64
+ },
65
+ )
66
+
67
+ // 表单列表页:AI 设计 CRF(上传 Word/PDF)
68
+ AgentHost.registerPage(
69
+ 'edc_form_list',
70
+ [{
71
+ id: 'crf_design',
72
+ workflow: 'edc_crf_design',
73
+ label: 'AI 设计 CRF',
74
+ host: 'edc',
75
+ prefilled: () => ({
76
+ sub_project_id: currentSubProjectId,
77
+ sort_start: nextFormSort,
78
+ }),
79
+ enabled: () => AgentHost.canOpen('edc_crf_design', { prefilled: { sub_project_id: currentSubProjectId } }),
80
+ hostContext: { refresh: 'form_list' },
81
+ }],
82
+ { refreshKey: 'form_list', onRefresh: () => refreshFormList() },
83
+ )
84
+
85
+ AgentHost.openAction('edc_patient_list', 'form_fill')
86
+
87
+ // 离开页时务必 unregister(keep-alive 切走 / destroy),避免后台误刷
88
+ AgentHost.unregisterPage('edc_patient_list')
89
+ ```
90
+
91
+ EDC / 专病库可用 `createAgentPageMixin` 自动处理进出页 register/unregister。
92
+
93
+ ### Widget 内提交流程(iframe,宿主无需处理文件)
94
+
95
+ 1. SDK 打开 `/host/widget?workflow=...` iframe
96
+ 2. Widget 拉取 `GET /host/workflows/{name}/ui` 渲染表单(勾选填表场景 Widget **仅**上传文书/文本)
97
+ 3. Widget 创建会话 `POST /sessions`
98
+ 4. 用户提交 → **`POST /host/scenarios/{name}/run`(multipart,一次完成)**
99
+ 5. Hub iframe 展示任务进度;成功完成后 SDK 对仍注册页触发 `onRefresh`
100
+
101
+ `complete` / `success` 来自 Agent 任务协议(Hub/Widget `postMessage`),与宿主业务 API 返回结构无关。
102
+
103
+ 带附件的工作流(如 Excel 批量填表)无需宿主先上传文件;附件在 Widget 内选好后随 scenario run 一并提交。
104
+
105
+ ## Bootstrap 契约(宿主 BFF)
106
+
107
+ `GET /agent/bootstrap?hosts=edc` 建议返回:
108
+
109
+ ```json
110
+ {
111
+ "agent_token": "...",
112
+ "refresh_token": "...",
113
+ "agent_web_origin": "https://agent.example.com"
114
+ }
115
+ ```
116
+
117
+ - `agent_web_origin`:Agent **前端**地址(iframe、Hub)
118
+ - Token 换取走 boot-admin → Agent API(`agent-host-api-base-url`),与 `agent_web_origin` 分离
119
+
120
+ 可选:`init({ agentOrigin: '...' })` 显式覆盖 BFF 下发的地址。
121
+
122
+ ## Scenario API(Headless / 自绘表单时)
123
+
124
+ iframe SDK 一般不需要宿主直调;若宿主自画表单、绕过 Widget,可按下列契约提交:
125
+
126
+ ```http
127
+ POST /api/v1/host/scenarios/{workflow_name}/run
128
+ Authorization: Bearer <token>
129
+ Content-Type: multipart/form-data
130
+ ```
131
+
132
+ | 字段 | 类型 | 必填 | 说明 |
133
+ |------|------|------|------|
134
+ | `session_id` | string | 是 | 已创建的 Agent 会话 UUID |
135
+ | `host` | string | 否 | 宿主标识,默认 `research_portal` |
136
+ | `host_context` | string (JSON) | 否 | 页面上下文、刷新回调等 |
137
+ | `prefilled` | string (JSON) | 否 | 宿主勾选带入的只读参数 |
138
+ | `form_values` | string (JSON) | 否 | 用户在表单中的选择 |
139
+ | `prompt_extra` | string | 否 | 附加 prompt |
140
+ | `attachment_files` | file[] | 否 | 附件,可多文件 |
141
+
142
+ 响应示例:
143
+
144
+ ```json
145
+ {
146
+ "success": true,
147
+ "data": {
148
+ "task_id": "uuid",
149
+ "session_id": "uuid",
150
+ "status": "queued",
151
+ "stream_url": "/api/v1/tasks/{task_id}/stream"
152
+ }
153
+ }
154
+ ```
155
+
156
+ 与 Chat 对比:
157
+
158
+ | 场景 | 接口 |
159
+ |------|------|
160
+ | Chat 发消息 + 附件 | `POST /sessions/{id}/messages-with-files` |
161
+ | Host Widget / Scenario | `POST /host/scenarios/{name}/run`(multipart) |
162
+
163
+ 完整契约见仓库 `PRD/11_host_widget_integration.md` §6。
@@ -0,0 +1,222 @@
1
+ var de=Object.defineProperty,fe=Object.defineProperties;var ge=Object.getOwnPropertyDescriptors;var U=Object.getOwnPropertySymbols;var pe=Object.prototype.hasOwnProperty,be=Object.prototype.propertyIsEnumerable;var _=(u,l,c)=>l in u?de(u,l,{enumerable:!0,configurable:!0,writable:!0,value:c}):u[l]=c,$=(u,l)=>{for(var c in l||(l={}))pe.call(l,c)&&_(u,c,l[c]);if(U)for(var c of U(l))be.call(l,c)&&_(u,c,l[c]);return u},F=(u,l)=>fe(u,ge(l));var h=(u,l,c)=>_(u,typeof l!="symbol"?l+"":l,c);var p=(u,l,c)=>new Promise((w,d)=>{var b=y=>{try{P(c.next(y))}catch(E){d(E)}},W=y=>{try{P(c.throw(y))}catch(E){d(E)}},P=y=>y.done?w(y.value):Promise.resolve(y.value).then(b,W);P((c=c.apply(u,l)).next())});(function(u){"use strict";const l="agent-host";function c(t){return typeof t=="object"&&t!==null&&t.channel===l&&typeof t.type=="string"}function w(t){return t==null?t:JSON.parse(JSON.stringify(t))}const d="agent-host-launcher",b="agent-host-hub-panel-overlay",W="agent-host-hub-iframe",P="agent-host-widget-overlay",y="agent-host-widget-iframe",E=400,j="agent-host-sdk-styles";function V(t){return t.replace(/\/$/,"")}function T(t){try{return new URL(t).origin}catch(e){return t.replace(/\/$/,"")}}function N(t,e){const n=T(t),i=T(e);if(n===i)return!0;try{const s=new URL(n),o=new URL(i),f=new Set(["localhost","127.0.0.1","[::1]"]);return s.protocol===o.protocol&&s.port===o.port&&f.has(s.hostname)&&f.has(o.hostname)}catch(s){return!1}}function G(){if(typeof document=="undefined"||document.getElementById(j))return;const t=document.createElement("style");t.id=j,t.textContent=`
2
+ #${d} {
3
+ position: fixed;
4
+ right: 24px;
5
+ bottom: 24px;
6
+ z-index: 2147482999;
7
+ width: 56px;
8
+ height: 56px;
9
+ border: none;
10
+ border-radius: 50%;
11
+ cursor: pointer;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ color: #fff;
16
+ font-size: 22px;
17
+ line-height: 1;
18
+ background: linear-gradient(135deg, #4f46e5, #7c3aed);
19
+ box-shadow: 0 4px 16px rgba(79, 70, 229, 0.45);
20
+ transition: transform 0.15s ease;
21
+ }
22
+ #${d}:hover { transform: scale(1.05); }
23
+ #${d}.is-pulse {
24
+ animation: agent-host-fab-pulse 2s ease-in-out infinite;
25
+ }
26
+ #${d} .agent-host-launcher-icon {
27
+ display: inline-block;
28
+ line-height: 1;
29
+ transform-origin: center center;
30
+ }
31
+ #${d}.is-breathe .agent-host-launcher-icon {
32
+ animation: agent-host-clover-breathe 1.8s ease-in-out infinite;
33
+ }
34
+ #${d} .agent-host-launcher-bell {
35
+ display: none;
36
+ width: 22px;
37
+ height: 22px;
38
+ line-height: 0;
39
+ transform-origin: top center;
40
+ }
41
+ #${d} .agent-host-launcher-bell.is-visible {
42
+ display: inline-block;
43
+ animation: agent-host-bell-shake 0.85s ease-in-out infinite;
44
+ }
45
+ #${d} .agent-host-launcher-bell svg {
46
+ width: 100%;
47
+ height: 100%;
48
+ display: block;
49
+ }
50
+ #${d} .agent-host-launcher-badge {
51
+ position: absolute;
52
+ top: -4px;
53
+ right: -4px;
54
+ box-sizing: border-box;
55
+ min-width: 20px;
56
+ height: 20px;
57
+ padding: 0 5px;
58
+ border-radius: 999px;
59
+ font-size: 11px;
60
+ font-weight: 600;
61
+ line-height: 1;
62
+ display: none;
63
+ align-items: center;
64
+ justify-content: center;
65
+ border: 2px solid #fff;
66
+ background: #f59e0b;
67
+ color: #fff;
68
+ }
69
+ #${d} .agent-host-launcher-badge.is-visible { display: flex; }
70
+ #${d} .agent-host-launcher-badge.is-waiting { background: #ef4444; }
71
+ #agent-host-launcher-hint {
72
+ position: fixed;
73
+ right: 24px;
74
+ bottom: 88px;
75
+ z-index: 2147482998;
76
+ max-width: min(800px, calc(100vw - 48px));
77
+ padding: 10px 14px;
78
+ border-radius: 10px;
79
+ font-size: 13px;
80
+ line-height: 1.45;
81
+ color: #333;
82
+ background: #fff;
83
+ border: 1px solid #e8e8ff;
84
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
85
+ display: none;
86
+ pointer-events: auto;
87
+ }
88
+ #agent-host-launcher-hint.is-visible { display: block; }
89
+ #agent-host-launcher-hint .agent-host-hint-inner {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 10px;
93
+ flex-wrap: wrap;
94
+ }
95
+ #agent-host-launcher-hint .agent-host-hint-icon {
96
+ flex-shrink: 0;
97
+ display: inline-flex;
98
+ align-items: center;
99
+ justify-content: center;
100
+ width: 20px;
101
+ height: 20px;
102
+ font-size: 16px;
103
+ line-height: 1;
104
+ color: #7c3aed;
105
+ transform-origin: center center;
106
+ animation: agent-host-hint-clover 4.5s ease-in-out infinite;
107
+ }
108
+ #agent-host-launcher-hint .agent-host-hint-text {
109
+ flex: 1;
110
+ min-width: 140px;
111
+ color: #333;
112
+ }
113
+ #agent-host-launcher-hint .agent-host-hint-actions {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 4px;
117
+ flex-shrink: 0;
118
+ }
119
+ #agent-host-launcher-hint .agent-host-hint-use {
120
+ border: 1px solid #7c3aed;
121
+ border-radius: 6px;
122
+ padding: 0 12px;
123
+ height: 28px;
124
+ font-size: 12px;
125
+ font-weight: 500;
126
+ color: #7c3aed;
127
+ background: transparent;
128
+ cursor: pointer;
129
+ }
130
+ #agent-host-launcher-hint .agent-host-hint-use:hover {
131
+ color: #fff;
132
+ background: #7c3aed;
133
+ }
134
+ #agent-host-launcher-hint .agent-host-hint-dismiss {
135
+ border: none;
136
+ border-radius: 6px;
137
+ padding: 0 8px;
138
+ height: 28px;
139
+ font-size: 12px;
140
+ color: #999;
141
+ background: transparent;
142
+ cursor: pointer;
143
+ }
144
+ #agent-host-launcher-hint .agent-host-hint-dismiss:hover { color: #666; }
145
+ #${b} {
146
+ position: fixed;
147
+ inset: 0;
148
+ z-index: 2147483000;
149
+ pointer-events: none;
150
+ opacity: 0;
151
+ transition: opacity 0.2s ease;
152
+ }
153
+ #${b}.is-open {
154
+ pointer-events: auto;
155
+ opacity: 1;
156
+ }
157
+ #${b} .agent-host-hub-backdrop {
158
+ position: absolute;
159
+ inset: 0;
160
+ background: rgba(15, 23, 42, 0.45);
161
+ }
162
+ #${b} .agent-host-hub-panel-shell {
163
+ position: absolute;
164
+ top: 0;
165
+ right: 0;
166
+ bottom: 0;
167
+ width: min(${E}px, 100vw);
168
+ transform: translateX(100%);
169
+ transition: transform 0.28s cubic-bezier(0.2, 0, 0, 1), width 0.28s cubic-bezier(0.2, 0, 0, 1);
170
+ background: #fff;
171
+ box-shadow: -8px 0 24px rgba(15, 23, 42, 0.14);
172
+ overflow: hidden;
173
+ }
174
+ #${b} .agent-host-hub-panel-shell.is-expanded {
175
+ width: min(50vw, 100vw);
176
+ }
177
+ #${b}.is-open .agent-host-hub-panel-shell {
178
+ transform: translateX(0);
179
+ }
180
+ #${b} iframe {
181
+ width: 100%;
182
+ height: 100%;
183
+ border: none;
184
+ background: #fff;
185
+ display: block;
186
+ }
187
+ @keyframes agent-host-fab-pulse {
188
+ 0%, 100% { box-shadow: 0 4px 16px rgba(79, 70, 229, 0.45); }
189
+ 50% { box-shadow: 0 4px 24px rgba(124, 58, 237, 0.65); }
190
+ }
191
+ @keyframes agent-host-clover-breathe {
192
+ 0%, 100% { transform: scale(1); }
193
+ 50% { transform: scale(1.28); }
194
+ }
195
+ /* 提示条四叶草:前 3s 呼吸 2 次(各 1.5s),后 1.5s 缓转一圈,循环 */
196
+ @keyframes agent-host-hint-clover {
197
+ 0% { transform: scale(0.88) rotate(0deg); }
198
+ 16.67% { transform: scale(1.12) rotate(0deg); }
199
+ 33.33% { transform: scale(0.88) rotate(0deg); }
200
+ 50% { transform: scale(1.12) rotate(0deg); }
201
+ 66.67% { transform: scale(1) rotate(0deg); }
202
+ 100% { transform: scale(1) rotate(360deg); }
203
+ }
204
+ @keyframes agent-host-bell-shake {
205
+ 0%, 100% { transform: rotate(0deg); }
206
+ 12% { transform: rotate(14deg); }
207
+ 24% { transform: rotate(-12deg); }
208
+ 36% { transform: rotate(10deg); }
209
+ 48% { transform: rotate(-8deg); }
210
+ 60% { transform: rotate(5deg); }
211
+ 72% { transform: rotate(-3deg); }
212
+ 84% { transform: rotate(1deg); }
213
+ }
214
+ `,document.head.appendChild(t)}class K{constructor(e,n,i){h(this,"agentOrigin","");h(this,"hubLauncher",null);h(this,"hubLauncherBadge",null);h(this,"hubLauncherHint",null);h(this,"hubPanelOverlay",null);h(this,"hubIframe",null);h(this,"widgetIframe",null);h(this,"hubReady",!1);h(this,"widgetReady",!1);h(this,"hubPanelOpen",!1);h(this,"hubPanelExpanded",!1);h(this,"lastRunning",0);h(this,"lastWaiting",0);h(this,"launcherHintAction",null);h(this,"pendingHubMessages",[]);h(this,"pendingWidgetMessages",[]);h(this,"onChildMessage");h(this,"onHintUse");h(this,"onHintDismiss");h(this,"handleMessage",e=>{var i,s;if(!N(this.agentOrigin,e.origin)||!c(e.data))return;const n=e.data;if(n.type==="ready"){n.surface==="hub"?(this.hubReady=!0,this.flush("hub")):(this.widgetReady=!0,this.flush("widget"));return}if(n.type==="hub-panel-close"){this.closeHubPanel();return}if(n.type==="hub-panel-width"){this.setHubPanelExpanded(!!n.expanded);return}if(n.type==="hub-stats"){this.updateLauncherStats(n.running,n.waiting),(i=this.onChildMessage)==null||i.call(this,n);return}(s=this.onChildMessage)==null||s.call(this,n)});this.agentOrigin=V(e),this.onChildMessage=n,this.onHintUse=i==null?void 0:i.onHintUse,this.onHintDismiss=i==null?void 0:i.onHintDismiss,window.addEventListener("message",this.handleMessage)}destroy(){window.removeEventListener("message",this.handleMessage),this.removeHub(),this.closeWidget()}mountHub(){this.hubPanelOverlay||typeof document=="undefined"||(G(),this.mountLauncher(),this.mountHubPanel())}openHubPanel(){var e;this.hubPanelOverlay&&(this.hubPanelOpen=!0,this.hubPanelOverlay.classList.add("is-open"),(e=this.hubLauncherHint)==null||e.classList.remove("is-visible"),this.post("hub",{channel:l,type:"open-hub-panel"}),this.applyLauncherVisual())}closeHubPanel(){this.hubPanelOverlay&&(this.hubPanelOpen=!1,this.hubPanelOverlay.classList.remove("is-open"),this.setHubPanelExpanded(!1),this.post("hub",{channel:l,type:"close-hub-panel"}),this.updateLauncherHint(this.launcherHintAction),this.applyLauncherVisual())}setHubPanelExpanded(e){var i;this.hubPanelExpanded=e;const n=(i=this.hubPanelOverlay)==null?void 0:i.querySelector(".agent-host-hub-panel-shell");n==null||n.classList.toggle("is-expanded",e)}updateLauncherHint(e){var s,o;this.launcherHintAction=e;const n=this.hubLauncherHint;if(!n)return;if(this.hubPanelOpen){n.classList.remove("is-visible");return}if(!e||!e.hint.trim()){n.innerHTML="",n.classList.remove("is-visible");return}n.innerHTML=`
215
+ <div class="agent-host-hint-inner">
216
+ <span class="agent-host-hint-icon" aria-hidden="true">✦</span>
217
+ <span class="agent-host-hint-text"></span>
218
+ <div class="agent-host-hint-actions">
219
+ <button type="button" class="agent-host-hint-use">立即开始</button>
220
+ <button type="button" class="agent-host-hint-dismiss">稍后</button>
221
+ </div>
222
+ </div>`;const i=n.querySelector(".agent-host-hint-text");i&&(i.textContent=e.hint),(s=n.querySelector(".agent-host-hint-use"))==null||s.addEventListener("click",f=>{var g;f.stopPropagation(),(g=this.onHintUse)==null||g.call(this,e.pageId,e.actionId)}),(o=n.querySelector(".agent-host-hint-dismiss"))==null||o.addEventListener("click",f=>{var g;f.stopPropagation(),(g=this.onHintDismiss)==null||g.call(this)}),n.classList.add("is-visible")}mountLauncher(){if(this.hubLauncher||document.getElementById(d))return;const e=document.createElement("button");if(e.id=d,e.type="button",e.title="科研AI助手",e.setAttribute("aria-label","科研AI助手"),e.innerHTML='<span class="agent-host-launcher-icon" aria-hidden="true">✦</span><span class="agent-host-launcher-bell" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 3a5 5 0 0 0-5 5v1.5c0 1.3-.4 2.6-1.2 3.7L4.5 15.5c-.4.5-.1 1.3.5 1.3h14c.6 0 .9-.8.5-1.3l-1.3-2.3A6.6 6.6 0 0 1 17 9.5V8a5 5 0 0 0-5-5Z" fill="#f59e0b"/><path d="M10 18.5a2 2 0 0 0 4 0" stroke="#f59e0b" stroke-width="1.8" stroke-linecap="round"/></svg></span><span class="agent-host-launcher-badge"></span>',e.addEventListener("click",()=>this.openHubPanel()),document.body.appendChild(e),this.hubLauncher=e,this.hubLauncherBadge=e.querySelector(".agent-host-launcher-badge"),document.getElementById("agent-host-launcher-hint"))this.hubLauncherHint=document.getElementById("agent-host-launcher-hint");else{const n=document.createElement("div");n.id="agent-host-launcher-hint",document.body.appendChild(n),this.hubLauncherHint=n}}mountHubPanel(){const e=document.createElement("div");e.id=b;const n=document.createElement("div");n.className="agent-host-hub-backdrop",n.addEventListener("click",()=>this.closeHubPanel());const i=document.createElement("div");i.className="agent-host-hub-panel-shell";const s=document.createElement("iframe");s.id=W,s.title="科研AI助手",s.src=`${this.agentOrigin}/host/hub?embed=1&panel=1`,s.allow="clipboard-read; clipboard-write",i.appendChild(s),e.appendChild(n),e.appendChild(i),document.body.appendChild(e),this.hubPanelOverlay=e,this.hubIframe=s}updateLauncherStats(e,n){this.lastRunning=e,this.lastWaiting=n,this.applyLauncherVisual()}applyLauncherVisual(){const e=this.hubLauncher,n=this.hubLauncherBadge;if(!e||!n)return;const i=e.querySelector(".agent-host-launcher-icon"),s=e.querySelector(".agent-host-launcher-bell"),o=this.lastRunning,f=this.lastWaiting,g=f>0&&!this.hubPanelOpen;e.classList.toggle("is-pulse",o>0),e.classList.toggle("is-breathe",(o>0||f>0)&&!g),g?(e.title="有待确认事项,点击打开助手",e.setAttribute("aria-label","有待确认事项,点击打开助手")):(e.title="科研AI助手",e.setAttribute("aria-label","科研AI助手")),i&&(i.style.display=g?"none":""),s&&s.classList.toggle("is-visible",g),o>0?(n.textContent=String(o),n.classList.add("is-visible"),n.classList.remove("is-waiting")):f>0?(n.textContent="!",n.classList.add("is-visible","is-waiting")):(n.textContent="",n.classList.remove("is-visible","is-waiting"))}removeHub(){var e,n,i;(e=document.getElementById(d))==null||e.remove(),(n=document.getElementById("agent-host-launcher-hint"))==null||n.remove(),(i=document.getElementById(b))==null||i.remove(),this.hubLauncher=null,this.hubLauncherBadge=null,this.hubLauncherHint=null,this.hubPanelOverlay=null,this.hubIframe=null,this.hubReady=!1,this.hubPanelOpen=!1,this.hubPanelExpanded=!1,this.pendingHubMessages=[]}openWidget(e,n,i){this.closeWidget();const s=document.createElement("div");s.id=P,s.style.cssText="position:fixed;inset:0;z-index:2147483646;background:rgba(15,23,42,0.45);display:flex;align-items:center;justify-content:center;padding:16px;",s.addEventListener("click",ue=>{ue.target===s&&this.closeWidget()});const o=document.createElement("iframe");o.id=y,o.title=e.title||e.workflow;const f=new URLSearchParams({embed:"1",workflow:e.workflow,host:e.host||"",title:e.title||""});o.src=`${this.agentOrigin}/host/widget?${f.toString()}`,o.style.cssText="width:min(560px,100%);height:80vh;border:none;border-radius:12px;background:#fff;box-shadow:0 20px 60px rgba(0,0,0,0.25);display:block;",s.appendChild(o),document.body.appendChild(s),this.widgetIframe=o,this.widgetReady=!1;const g={channel:l,type:"init",token:n,refreshToken:i},he={workflow:e.workflow,title:e.title,host:e.host,prefilled:e.prefilled?w(e.prefilled):void 0,hostContext:e.hostContext?w(e.hostContext):void 0,sessionId:e.sessionId,preferSandboxFiles:e.preferSandboxFiles},ce={channel:l,type:"open-widget",payload:he};this.pendingWidgetMessages=[g,ce]}closeWidget(){var e;(e=document.getElementById(P))==null||e.remove(),this.widgetIframe=null,this.widgetReady=!1,this.pendingWidgetMessages=[]}sendToHub(e){this.post("hub",e)}post(e,n){var f;const i=e==="hub"?this.hubReady:this.widgetReady,s=e==="hub"?this.pendingHubMessages:this.pendingWidgetMessages;if(!i){s.push(n);return}const o=e==="hub"?this.hubIframe:this.widgetIframe;(f=o==null?void 0:o.contentWindow)==null||f.postMessage(n,T(this.agentOrigin))}flush(e){const n=e==="hub"?this.pendingHubMessages:this.pendingWidgetMessages,i=e==="hub"?this.hubIframe:this.widgetIframe;if(!(i!=null&&i.contentWindow))return;const s=T(this.agentOrigin);for(;n.length;){const o=n.shift();o&&i.contentWindow.postMessage(o,s)}}}function k(t){return typeof t=="function"?t():t}const H={},I={};let m=null,O=!1;function Y(t,e){const n=(e||"").trim();if(n)return n;for(const i of t){if(!i.hostContext)continue;const s=k(i.hostContext),o=s&&typeof s=="object"?String(s.refresh||"").trim():"";if(o)return o}}function J(t,e,n={}){const i=t!==m;i&&(O=!1),H[t]=e,m=t,!i&&e.some(o=>!o.enabled||o.enabled())&&(O=!1);const s=Y(e,n.refreshKey);s&&typeof n.onRefresh=="function"?I[t]={refreshKey:s,onRefresh:n.onRefresh}:delete I[t]}function X(t){delete H[t],delete I[t],m===t&&(m=null)}function Z(t){const e=String(t||"").trim();if(e){for(const n of Object.values(I))if(n.refreshKey===e)try{n.onRefresh()}catch(i){}}}function Q(){for(const t of Object.keys(H))delete H[t];for(const t of Object.keys(I))delete I[t];m=null,O=!1}function ee(t){t!==m&&(O=!1),m=t}function te(){O=!0}function z(t){return t.map(e=>({id:e.id,workflow:e.workflow,label:e.label,host:e.host,hint:e.hint?k(e.hint):void 0,hostContext:e.hostContext?w(k(e.hostContext)):void 0,prefilled:e.prefilled?w(k(e.prefilled)):void 0,enabled:e.enabled?e.enabled():!0}))}function B(t,e){const n=(H[t]||[]).find(o=>o.id===e);if(!n||n.enabled&&!n.enabled())return null;const i=n.prefilled?w(k(n.prefilled)):void 0,s=(n.workflow||"").trim();if(s==="edc_form_fill"&&i&&typeof i=="object"){const o=i;if(!o.sub_project_id&&!o.subProjectId)return null}if(s==="edc_crf_design"&&i&&typeof i=="object"){const o=i;if(!o.sub_project_id&&!o.subProjectId)return null}return{workflow:n.workflow,title:n.label,host:n.host,prefilled:i,hostContext:n.hostContext?w(k(n.hostContext)):void 0}}function ne(){return m}function ie(){return Object.entries(H).map(([t,e])=>({pageId:t,actions:e}))}function se(){if(O||!m)return null;const t=H[m]||[];for(const e of t){if(e.enabled&&!e.enabled())continue;const n=e.hint?k(e.hint):`可使用「${e.label}」`;return{pageId:m,actionId:e.id,hint:n}}return null}let r={},a=null,x="",A="",v=null;function oe(){const t=(r.agentOrigin||"").replace(/\/$/,"");if(!t)throw new Error("Agent 前端地址未配置:请在 init({ agentOrigin }) 传入,或由宿主 BFF bootstrap 返回 agent_web_origin");return t}function D(t){var e;t.agent_web_origin&&(r.agentOrigin=t.agent_web_origin.replace(/\/$/,"")),t.agent_token&&(x=t.agent_token),t.refresh_token&&(A=t.refresh_token),(e=r.onBootstrap)==null||e.call(r,t)}function R(){return p(this,null,function*(){if(r.getToken)return x=yield r.getToken(),x;if(x)return x;if(r.bootstrap){const t=yield r.bootstrap();if(D(t),x)return x}throw new Error("未连接 Agent:请配置 bootstrap 或 getToken")})}function C(){if(r.agentOrigin)try{L().updateLauncherHint(se())}catch(t){}}function L(){const t=oe();return a||(a=new K(t,re,{onHintUse:(e,n)=>{const i=B(e,n);i&&M(i)},onHintDismiss:()=>{te(),C()}}),r.showHub!==!1&&a.mountHub()),a}function ae(){if(!(!a||!r.agentOrigin)){for(const{pageId:t,actions:e}of ie())a.sendToHub({channel:"agent-host",type:"register-page",pageId:t,actions:z(e)});a.sendToHub({channel:"agent-host",type:"set-page",pageId:ne()}),C()}}function re(t){var e;if(t.type==="ready"&&t.surface==="hub"&&R().then(n=>{a==null||a.sendToHub({channel:"agent-host",type:"init",token:n,refreshToken:A||void 0}),ae()}).catch(()=>{}),t.type==="started"&&(a==null||a.closeWidget(),a==null||a.openHubPanel(),a==null||a.sendToHub({channel:"agent-host",type:"task-started",taskId:t.taskId,sessionId:t.sessionId,workflow:t.workflow,label:t.label||t.workflow,formValues:t.formValues,prefilled:t.prefilled,hostContext:t.hostContext,hubContinue:t.hubContinue})),t.type==="complete"){if(t.status==="success"){const n=t.hostContext&&t.hostContext.refresh;Z(n)}(e=r.onTaskComplete)==null||e.call(r,{taskId:t.taskId,status:t.status,hostContext:t.hostContext}),a==null||a.closeWidget()}if(t.type==="close"&&(a==null||a.closeWidget()),t.type==="open-widget"&&M(t.payload),t.type==="open-action"){const n=B(t.pageId,t.actionId);n&&M(n)}t.type==="hub-stats"&&(t.running,a==null||a.updateLauncherStats(t.running,t.waiting),C())}function M(t){return p(this,null,function*(){yield S(),r.prepareOpenWidget&&(yield r.prepareOpenWidget());const e=L(),n=yield R();e.openWidget({workflow:t.workflow,title:t.title||t.label,host:t.host||r.host,prefilled:t.prefilled,hostContext:t.hostContext,sessionId:t.sessionId,preferSandboxFiles:t.preferSandboxFiles},n,A||void 0)})}function S(){return p(this,null,function*(){v&&(yield v)})}function le(t){return p(this,null,function*(){if(r=$({showHub:!0},t),r.bootstrap){const e=yield r.bootstrap();D(e)}if(r.agentOrigin&&r.showHub!==!1&&typeof document!="undefined")try{L();const e=yield R();a==null||a.sendToHub({channel:"agent-host",type:"init",token:e,refreshToken:A||void 0})}catch(e){}})}const q={init(t){return p(this,null,function*(){v&&(yield v),v=le(t).finally(()=>{v=null}),yield v})},getToken(){return p(this,null,function*(){return yield S(),R()})},registerPage(i,s){return p(this,arguments,function*(t,e,n={}){yield S(),J(t,e,n),r.agentOrigin&&L().sendToHub({channel:"agent-host",type:"register-page",pageId:t,actions:z(e)}),C()})},unregisterPage(t){return p(this,null,function*(){yield S(),X(t),r.agentOrigin&&(L().sendToHub({channel:"agent-host",type:"unregister-page",pageId:t}),L().sendToHub({channel:"agent-host",type:"set-page",pageId:null})),C()})},setCurrentPage(t){return p(this,null,function*(){yield S(),ee(t),r.agentOrigin&&L().sendToHub({channel:"agent-host",type:"set-page",pageId:t}),C()})},openAction(t,e){return p(this,null,function*(){const n=B(t,e);if(!n)throw new Error("当前操作不可用");yield M(n)})},openWorkflow(n){return p(this,arguments,function*(t,e={}){yield M(F($({workflow:t},e),{host:e.host||r.host,title:e.title||e.label||t}))})},canOpen(t,e={}){const n=(t||"").trim(),i=e.prefilled||{},s=String(i.sub_project_id||i.subProjectId||"").trim();if(n==="edc_form_fill"){const o=i.forms;return!!(s&&Array.isArray(o)&&o.length>0)}return n==="edc_crf_design"?!!s:!0},destroy(){a==null||a.destroy(),a=null,x="",A="",r={},v=null,Q()}};typeof window!="undefined"&&(window.AgentHost=q),u.AGENT_HOST_MSG=l,u.AgentHost=q,u.isAgentHostMessage=c,Object.defineProperty(u,Symbol.toStringTag,{value:"Module"})})(this.AgentHost=this.AgentHost||{});
@@ -0,0 +1,772 @@
1
+ var q = Object.defineProperty, U = Object.defineProperties;
2
+ var F = Object.getOwnPropertyDescriptors;
3
+ var W = Object.getOwnPropertySymbols;
4
+ var V = Object.prototype.hasOwnProperty, N = Object.prototype.propertyIsEnumerable;
5
+ var R = (t, e, n) => e in t ? q(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n, S = (t, e) => {
6
+ for (var n in e || (e = {}))
7
+ V.call(e, n) && R(t, n, e[n]);
8
+ if (W)
9
+ for (var n of W(e))
10
+ N.call(e, n) && R(t, n, e[n]);
11
+ return t;
12
+ }, B = (t, e) => U(t, F(e));
13
+ var l = (t, e, n) => R(t, typeof e != "symbol" ? e + "" : e, n);
14
+ var d = (t, e, n) => new Promise((i, s) => {
15
+ var o = (g) => {
16
+ try {
17
+ c(n.next(g));
18
+ } catch (P) {
19
+ s(P);
20
+ }
21
+ }, h = (g) => {
22
+ try {
23
+ c(n.throw(g));
24
+ } catch (P) {
25
+ s(P);
26
+ }
27
+ }, c = (g) => g.done ? i(g.value) : Promise.resolve(g.value).then(o, h);
28
+ c((n = n.apply(t, e)).next());
29
+ });
30
+ const I = "agent-host";
31
+ function K(t) {
32
+ return typeof t == "object" && t !== null && t.channel === I && typeof t.type == "string";
33
+ }
34
+ function k(t) {
35
+ return t == null ? t : JSON.parse(JSON.stringify(t));
36
+ }
37
+ const u = "agent-host-launcher", p = "agent-host-hub-panel-overlay", G = "agent-host-hub-iframe", $ = "agent-host-widget-overlay", Y = "agent-host-widget-iframe", J = 400, _ = "agent-host-sdk-styles";
38
+ function X(t) {
39
+ return t.replace(/\/$/, "");
40
+ }
41
+ function A(t) {
42
+ try {
43
+ return new URL(t).origin;
44
+ } catch (e) {
45
+ return t.replace(/\/$/, "");
46
+ }
47
+ }
48
+ function Z(t, e) {
49
+ const n = A(t), i = A(e);
50
+ if (n === i) return !0;
51
+ try {
52
+ const s = new URL(n), o = new URL(i), h = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
53
+ return s.protocol === o.protocol && s.port === o.port && h.has(s.hostname) && h.has(o.hostname);
54
+ } catch (s) {
55
+ return !1;
56
+ }
57
+ }
58
+ function Q() {
59
+ if (typeof document == "undefined" || document.getElementById(_)) return;
60
+ const t = document.createElement("style");
61
+ t.id = _, t.textContent = `
62
+ #${u} {
63
+ position: fixed;
64
+ right: 24px;
65
+ bottom: 24px;
66
+ z-index: 2147482999;
67
+ width: 56px;
68
+ height: 56px;
69
+ border: none;
70
+ border-radius: 50%;
71
+ cursor: pointer;
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ color: #fff;
76
+ font-size: 22px;
77
+ line-height: 1;
78
+ background: linear-gradient(135deg, #4f46e5, #7c3aed);
79
+ box-shadow: 0 4px 16px rgba(79, 70, 229, 0.45);
80
+ transition: transform 0.15s ease;
81
+ }
82
+ #${u}:hover { transform: scale(1.05); }
83
+ #${u}.is-pulse {
84
+ animation: agent-host-fab-pulse 2s ease-in-out infinite;
85
+ }
86
+ #${u} .agent-host-launcher-icon {
87
+ display: inline-block;
88
+ line-height: 1;
89
+ transform-origin: center center;
90
+ }
91
+ #${u}.is-breathe .agent-host-launcher-icon {
92
+ animation: agent-host-clover-breathe 1.8s ease-in-out infinite;
93
+ }
94
+ #${u} .agent-host-launcher-bell {
95
+ display: none;
96
+ width: 22px;
97
+ height: 22px;
98
+ line-height: 0;
99
+ transform-origin: top center;
100
+ }
101
+ #${u} .agent-host-launcher-bell.is-visible {
102
+ display: inline-block;
103
+ animation: agent-host-bell-shake 0.85s ease-in-out infinite;
104
+ }
105
+ #${u} .agent-host-launcher-bell svg {
106
+ width: 100%;
107
+ height: 100%;
108
+ display: block;
109
+ }
110
+ #${u} .agent-host-launcher-badge {
111
+ position: absolute;
112
+ top: -4px;
113
+ right: -4px;
114
+ box-sizing: border-box;
115
+ min-width: 20px;
116
+ height: 20px;
117
+ padding: 0 5px;
118
+ border-radius: 999px;
119
+ font-size: 11px;
120
+ font-weight: 600;
121
+ line-height: 1;
122
+ display: none;
123
+ align-items: center;
124
+ justify-content: center;
125
+ border: 2px solid #fff;
126
+ background: #f59e0b;
127
+ color: #fff;
128
+ }
129
+ #${u} .agent-host-launcher-badge.is-visible { display: flex; }
130
+ #${u} .agent-host-launcher-badge.is-waiting { background: #ef4444; }
131
+ #agent-host-launcher-hint {
132
+ position: fixed;
133
+ right: 24px;
134
+ bottom: 88px;
135
+ z-index: 2147482998;
136
+ max-width: min(800px, calc(100vw - 48px));
137
+ padding: 10px 14px;
138
+ border-radius: 10px;
139
+ font-size: 13px;
140
+ line-height: 1.45;
141
+ color: #333;
142
+ background: #fff;
143
+ border: 1px solid #e8e8ff;
144
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
145
+ display: none;
146
+ pointer-events: auto;
147
+ }
148
+ #agent-host-launcher-hint.is-visible { display: block; }
149
+ #agent-host-launcher-hint .agent-host-hint-inner {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 10px;
153
+ flex-wrap: wrap;
154
+ }
155
+ #agent-host-launcher-hint .agent-host-hint-icon {
156
+ flex-shrink: 0;
157
+ display: inline-flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ width: 20px;
161
+ height: 20px;
162
+ font-size: 16px;
163
+ line-height: 1;
164
+ color: #7c3aed;
165
+ transform-origin: center center;
166
+ animation: agent-host-hint-clover 4.5s ease-in-out infinite;
167
+ }
168
+ #agent-host-launcher-hint .agent-host-hint-text {
169
+ flex: 1;
170
+ min-width: 140px;
171
+ color: #333;
172
+ }
173
+ #agent-host-launcher-hint .agent-host-hint-actions {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 4px;
177
+ flex-shrink: 0;
178
+ }
179
+ #agent-host-launcher-hint .agent-host-hint-use {
180
+ border: 1px solid #7c3aed;
181
+ border-radius: 6px;
182
+ padding: 0 12px;
183
+ height: 28px;
184
+ font-size: 12px;
185
+ font-weight: 500;
186
+ color: #7c3aed;
187
+ background: transparent;
188
+ cursor: pointer;
189
+ }
190
+ #agent-host-launcher-hint .agent-host-hint-use:hover {
191
+ color: #fff;
192
+ background: #7c3aed;
193
+ }
194
+ #agent-host-launcher-hint .agent-host-hint-dismiss {
195
+ border: none;
196
+ border-radius: 6px;
197
+ padding: 0 8px;
198
+ height: 28px;
199
+ font-size: 12px;
200
+ color: #999;
201
+ background: transparent;
202
+ cursor: pointer;
203
+ }
204
+ #agent-host-launcher-hint .agent-host-hint-dismiss:hover { color: #666; }
205
+ #${p} {
206
+ position: fixed;
207
+ inset: 0;
208
+ z-index: 2147483000;
209
+ pointer-events: none;
210
+ opacity: 0;
211
+ transition: opacity 0.2s ease;
212
+ }
213
+ #${p}.is-open {
214
+ pointer-events: auto;
215
+ opacity: 1;
216
+ }
217
+ #${p} .agent-host-hub-backdrop {
218
+ position: absolute;
219
+ inset: 0;
220
+ background: rgba(15, 23, 42, 0.45);
221
+ }
222
+ #${p} .agent-host-hub-panel-shell {
223
+ position: absolute;
224
+ top: 0;
225
+ right: 0;
226
+ bottom: 0;
227
+ width: min(${J}px, 100vw);
228
+ transform: translateX(100%);
229
+ transition: transform 0.28s cubic-bezier(0.2, 0, 0, 1), width 0.28s cubic-bezier(0.2, 0, 0, 1);
230
+ background: #fff;
231
+ box-shadow: -8px 0 24px rgba(15, 23, 42, 0.14);
232
+ overflow: hidden;
233
+ }
234
+ #${p} .agent-host-hub-panel-shell.is-expanded {
235
+ width: min(50vw, 100vw);
236
+ }
237
+ #${p}.is-open .agent-host-hub-panel-shell {
238
+ transform: translateX(0);
239
+ }
240
+ #${p} iframe {
241
+ width: 100%;
242
+ height: 100%;
243
+ border: none;
244
+ background: #fff;
245
+ display: block;
246
+ }
247
+ @keyframes agent-host-fab-pulse {
248
+ 0%, 100% { box-shadow: 0 4px 16px rgba(79, 70, 229, 0.45); }
249
+ 50% { box-shadow: 0 4px 24px rgba(124, 58, 237, 0.65); }
250
+ }
251
+ @keyframes agent-host-clover-breathe {
252
+ 0%, 100% { transform: scale(1); }
253
+ 50% { transform: scale(1.28); }
254
+ }
255
+ /* 提示条四叶草:前 3s 呼吸 2 次(各 1.5s),后 1.5s 缓转一圈,循环 */
256
+ @keyframes agent-host-hint-clover {
257
+ 0% { transform: scale(0.88) rotate(0deg); }
258
+ 16.67% { transform: scale(1.12) rotate(0deg); }
259
+ 33.33% { transform: scale(0.88) rotate(0deg); }
260
+ 50% { transform: scale(1.12) rotate(0deg); }
261
+ 66.67% { transform: scale(1) rotate(0deg); }
262
+ 100% { transform: scale(1) rotate(360deg); }
263
+ }
264
+ @keyframes agent-host-bell-shake {
265
+ 0%, 100% { transform: rotate(0deg); }
266
+ 12% { transform: rotate(14deg); }
267
+ 24% { transform: rotate(-12deg); }
268
+ 36% { transform: rotate(10deg); }
269
+ 48% { transform: rotate(-8deg); }
270
+ 60% { transform: rotate(5deg); }
271
+ 72% { transform: rotate(-3deg); }
272
+ 84% { transform: rotate(1deg); }
273
+ }
274
+ `, document.head.appendChild(t);
275
+ }
276
+ class ee {
277
+ constructor(e, n, i) {
278
+ l(this, "agentOrigin", "");
279
+ l(this, "hubLauncher", null);
280
+ l(this, "hubLauncherBadge", null);
281
+ l(this, "hubLauncherHint", null);
282
+ l(this, "hubPanelOverlay", null);
283
+ l(this, "hubIframe", null);
284
+ l(this, "widgetIframe", null);
285
+ l(this, "hubReady", !1);
286
+ l(this, "widgetReady", !1);
287
+ l(this, "hubPanelOpen", !1);
288
+ l(this, "hubPanelExpanded", !1);
289
+ l(this, "lastRunning", 0);
290
+ l(this, "lastWaiting", 0);
291
+ l(this, "launcherHintAction", null);
292
+ l(this, "pendingHubMessages", []);
293
+ l(this, "pendingWidgetMessages", []);
294
+ l(this, "onChildMessage");
295
+ l(this, "onHintUse");
296
+ l(this, "onHintDismiss");
297
+ l(this, "handleMessage", (e) => {
298
+ var i, s;
299
+ if (!Z(this.agentOrigin, e.origin) || !K(e.data)) return;
300
+ const n = e.data;
301
+ if (n.type === "ready") {
302
+ n.surface === "hub" ? (this.hubReady = !0, this.flush("hub")) : (this.widgetReady = !0, this.flush("widget"));
303
+ return;
304
+ }
305
+ if (n.type === "hub-panel-close") {
306
+ this.closeHubPanel();
307
+ return;
308
+ }
309
+ if (n.type === "hub-panel-width") {
310
+ this.setHubPanelExpanded(!!n.expanded);
311
+ return;
312
+ }
313
+ if (n.type === "hub-stats") {
314
+ this.updateLauncherStats(n.running, n.waiting), (i = this.onChildMessage) == null || i.call(this, n);
315
+ return;
316
+ }
317
+ (s = this.onChildMessage) == null || s.call(this, n);
318
+ });
319
+ this.agentOrigin = X(e), this.onChildMessage = n, this.onHintUse = i == null ? void 0 : i.onHintUse, this.onHintDismiss = i == null ? void 0 : i.onHintDismiss, window.addEventListener("message", this.handleMessage);
320
+ }
321
+ destroy() {
322
+ window.removeEventListener("message", this.handleMessage), this.removeHub(), this.closeWidget();
323
+ }
324
+ /** Launcher(宿主 DOM)+ 固定尺寸 Hub Panel(显隐用 CSS,不改 iframe 尺寸) */
325
+ mountHub() {
326
+ this.hubPanelOverlay || typeof document == "undefined" || (Q(), this.mountLauncher(), this.mountHubPanel());
327
+ }
328
+ openHubPanel() {
329
+ var e;
330
+ this.hubPanelOverlay && (this.hubPanelOpen = !0, this.hubPanelOverlay.classList.add("is-open"), (e = this.hubLauncherHint) == null || e.classList.remove("is-visible"), this.post("hub", { channel: I, type: "open-hub-panel" }), this.applyLauncherVisual());
331
+ }
332
+ closeHubPanel() {
333
+ this.hubPanelOverlay && (this.hubPanelOpen = !1, this.hubPanelOverlay.classList.remove("is-open"), this.setHubPanelExpanded(!1), this.post("hub", { channel: I, type: "close-hub-panel" }), this.updateLauncherHint(this.launcherHintAction), this.applyLauncherVisual());
334
+ }
335
+ setHubPanelExpanded(e) {
336
+ var i;
337
+ this.hubPanelExpanded = e;
338
+ const n = (i = this.hubPanelOverlay) == null ? void 0 : i.querySelector(".agent-host-hub-panel-shell");
339
+ n == null || n.classList.toggle("is-expanded", e);
340
+ }
341
+ updateLauncherHint(e) {
342
+ var s, o;
343
+ this.launcherHintAction = e;
344
+ const n = this.hubLauncherHint;
345
+ if (!n) return;
346
+ if (this.hubPanelOpen) {
347
+ n.classList.remove("is-visible");
348
+ return;
349
+ }
350
+ if (!e || !e.hint.trim()) {
351
+ n.innerHTML = "", n.classList.remove("is-visible");
352
+ return;
353
+ }
354
+ n.innerHTML = `
355
+ <div class="agent-host-hint-inner">
356
+ <span class="agent-host-hint-icon" aria-hidden="true">✦</span>
357
+ <span class="agent-host-hint-text"></span>
358
+ <div class="agent-host-hint-actions">
359
+ <button type="button" class="agent-host-hint-use">立即开始</button>
360
+ <button type="button" class="agent-host-hint-dismiss">稍后</button>
361
+ </div>
362
+ </div>`;
363
+ const i = n.querySelector(".agent-host-hint-text");
364
+ i && (i.textContent = e.hint), (s = n.querySelector(".agent-host-hint-use")) == null || s.addEventListener("click", (h) => {
365
+ var c;
366
+ h.stopPropagation(), (c = this.onHintUse) == null || c.call(this, e.pageId, e.actionId);
367
+ }), (o = n.querySelector(".agent-host-hint-dismiss")) == null || o.addEventListener("click", (h) => {
368
+ var c;
369
+ h.stopPropagation(), (c = this.onHintDismiss) == null || c.call(this);
370
+ }), n.classList.add("is-visible");
371
+ }
372
+ mountLauncher() {
373
+ if (this.hubLauncher || document.getElementById(u)) return;
374
+ const e = document.createElement("button");
375
+ if (e.id = u, e.type = "button", e.title = "科研AI助手", e.setAttribute("aria-label", "科研AI助手"), e.innerHTML = '<span class="agent-host-launcher-icon" aria-hidden="true">✦</span><span class="agent-host-launcher-bell" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 3a5 5 0 0 0-5 5v1.5c0 1.3-.4 2.6-1.2 3.7L4.5 15.5c-.4.5-.1 1.3.5 1.3h14c.6 0 .9-.8.5-1.3l-1.3-2.3A6.6 6.6 0 0 1 17 9.5V8a5 5 0 0 0-5-5Z" fill="#f59e0b"/><path d="M10 18.5a2 2 0 0 0 4 0" stroke="#f59e0b" stroke-width="1.8" stroke-linecap="round"/></svg></span><span class="agent-host-launcher-badge"></span>', e.addEventListener("click", () => this.openHubPanel()), document.body.appendChild(e), this.hubLauncher = e, this.hubLauncherBadge = e.querySelector(".agent-host-launcher-badge"), document.getElementById("agent-host-launcher-hint"))
376
+ this.hubLauncherHint = document.getElementById("agent-host-launcher-hint");
377
+ else {
378
+ const n = document.createElement("div");
379
+ n.id = "agent-host-launcher-hint", document.body.appendChild(n), this.hubLauncherHint = n;
380
+ }
381
+ }
382
+ mountHubPanel() {
383
+ const e = document.createElement("div");
384
+ e.id = p;
385
+ const n = document.createElement("div");
386
+ n.className = "agent-host-hub-backdrop", n.addEventListener("click", () => this.closeHubPanel());
387
+ const i = document.createElement("div");
388
+ i.className = "agent-host-hub-panel-shell";
389
+ const s = document.createElement("iframe");
390
+ s.id = G, s.title = "科研AI助手", s.src = `${this.agentOrigin}/host/hub?embed=1&panel=1`, s.allow = "clipboard-read; clipboard-write", i.appendChild(s), e.appendChild(n), e.appendChild(i), document.body.appendChild(e), this.hubPanelOverlay = e, this.hubIframe = s;
391
+ }
392
+ updateLauncherStats(e, n) {
393
+ this.lastRunning = e, this.lastWaiting = n, this.applyLauncherVisual();
394
+ }
395
+ applyLauncherVisual() {
396
+ const e = this.hubLauncher, n = this.hubLauncherBadge;
397
+ if (!e || !n) return;
398
+ const i = e.querySelector(".agent-host-launcher-icon"), s = e.querySelector(".agent-host-launcher-bell"), o = this.lastRunning, h = this.lastWaiting, c = h > 0 && !this.hubPanelOpen;
399
+ e.classList.toggle("is-pulse", o > 0), e.classList.toggle("is-breathe", (o > 0 || h > 0) && !c), c ? (e.title = "有待确认事项,点击打开助手", e.setAttribute("aria-label", "有待确认事项,点击打开助手")) : (e.title = "科研AI助手", e.setAttribute("aria-label", "科研AI助手")), i && (i.style.display = c ? "none" : ""), s && s.classList.toggle("is-visible", c), o > 0 ? (n.textContent = String(o), n.classList.add("is-visible"), n.classList.remove("is-waiting")) : h > 0 ? (n.textContent = "!", n.classList.add("is-visible", "is-waiting")) : (n.textContent = "", n.classList.remove("is-visible", "is-waiting"));
400
+ }
401
+ removeHub() {
402
+ var e, n, i;
403
+ (e = document.getElementById(u)) == null || e.remove(), (n = document.getElementById("agent-host-launcher-hint")) == null || n.remove(), (i = document.getElementById(p)) == null || i.remove(), this.hubLauncher = null, this.hubLauncherBadge = null, this.hubLauncherHint = null, this.hubPanelOverlay = null, this.hubIframe = null, this.hubReady = !1, this.hubPanelOpen = !1, this.hubPanelExpanded = !1, this.pendingHubMessages = [];
404
+ }
405
+ openWidget(e, n, i) {
406
+ this.closeWidget();
407
+ const s = document.createElement("div");
408
+ s.id = $, s.style.cssText = "position:fixed;inset:0;z-index:2147483646;background:rgba(15,23,42,0.45);display:flex;align-items:center;justify-content:center;padding:16px;", s.addEventListener("click", (D) => {
409
+ D.target === s && this.closeWidget();
410
+ });
411
+ const o = document.createElement("iframe");
412
+ o.id = Y, o.title = e.title || e.workflow;
413
+ const h = new URLSearchParams({
414
+ embed: "1",
415
+ workflow: e.workflow,
416
+ host: e.host || "",
417
+ title: e.title || ""
418
+ });
419
+ o.src = `${this.agentOrigin}/host/widget?${h.toString()}`, o.style.cssText = "width:min(560px,100%);height:80vh;border:none;border-radius:12px;background:#fff;box-shadow:0 20px 60px rgba(0,0,0,0.25);display:block;", s.appendChild(o), document.body.appendChild(s), this.widgetIframe = o, this.widgetReady = !1;
420
+ const c = {
421
+ channel: I,
422
+ type: "init",
423
+ token: n,
424
+ refreshToken: i
425
+ }, g = {
426
+ workflow: e.workflow,
427
+ title: e.title,
428
+ host: e.host,
429
+ prefilled: e.prefilled ? k(e.prefilled) : void 0,
430
+ hostContext: e.hostContext ? k(e.hostContext) : void 0,
431
+ sessionId: e.sessionId,
432
+ preferSandboxFiles: e.preferSandboxFiles
433
+ }, P = {
434
+ channel: I,
435
+ type: "open-widget",
436
+ payload: g
437
+ };
438
+ this.pendingWidgetMessages = [c, P];
439
+ }
440
+ closeWidget() {
441
+ var e;
442
+ (e = document.getElementById($)) == null || e.remove(), this.widgetIframe = null, this.widgetReady = !1, this.pendingWidgetMessages = [];
443
+ }
444
+ sendToHub(e) {
445
+ this.post("hub", e);
446
+ }
447
+ post(e, n) {
448
+ var h;
449
+ const i = e === "hub" ? this.hubReady : this.widgetReady, s = e === "hub" ? this.pendingHubMessages : this.pendingWidgetMessages;
450
+ if (!i) {
451
+ s.push(n);
452
+ return;
453
+ }
454
+ const o = e === "hub" ? this.hubIframe : this.widgetIframe;
455
+ (h = o == null ? void 0 : o.contentWindow) == null || h.postMessage(n, A(this.agentOrigin));
456
+ }
457
+ flush(e) {
458
+ const n = e === "hub" ? this.pendingHubMessages : this.pendingWidgetMessages, i = e === "hub" ? this.hubIframe : this.widgetIframe;
459
+ if (!(i != null && i.contentWindow)) return;
460
+ const s = A(this.agentOrigin);
461
+ for (; n.length; ) {
462
+ const o = n.shift();
463
+ o && i.contentWindow.postMessage(o, s);
464
+ }
465
+ }
466
+ }
467
+ function w(t) {
468
+ return typeof t == "function" ? t() : t;
469
+ }
470
+ const x = {}, H = {};
471
+ let f = null, L = !1;
472
+ function te(t, e) {
473
+ const n = (e || "").trim();
474
+ if (n) return n;
475
+ for (const i of t) {
476
+ if (!i.hostContext) continue;
477
+ const s = w(i.hostContext), o = s && typeof s == "object" ? String(s.refresh || "").trim() : "";
478
+ if (o) return o;
479
+ }
480
+ }
481
+ function ne(t, e, n = {}) {
482
+ const i = t !== f;
483
+ i && (L = !1), x[t] = e, f = t, !i && e.some((o) => !o.enabled || o.enabled()) && (L = !1);
484
+ const s = te(e, n.refreshKey);
485
+ s && typeof n.onRefresh == "function" ? H[t] = { refreshKey: s, onRefresh: n.onRefresh } : delete H[t];
486
+ }
487
+ function ie(t) {
488
+ delete x[t], delete H[t], f === t && (f = null);
489
+ }
490
+ function se(t) {
491
+ const e = String(t || "").trim();
492
+ if (e) {
493
+ for (const n of Object.values(H))
494
+ if (n.refreshKey === e)
495
+ try {
496
+ n.onRefresh();
497
+ } catch (i) {
498
+ }
499
+ }
500
+ }
501
+ function oe() {
502
+ for (const t of Object.keys(x)) delete x[t];
503
+ for (const t of Object.keys(H)) delete H[t];
504
+ f = null, L = !1;
505
+ }
506
+ function ae(t) {
507
+ t !== f && (L = !1), f = t;
508
+ }
509
+ function re() {
510
+ L = !0;
511
+ }
512
+ function j(t) {
513
+ return t.map((e) => ({
514
+ id: e.id,
515
+ workflow: e.workflow,
516
+ label: e.label,
517
+ host: e.host,
518
+ hint: e.hint ? w(e.hint) : void 0,
519
+ hostContext: e.hostContext ? k(w(e.hostContext)) : void 0,
520
+ prefilled: e.prefilled ? k(w(e.prefilled)) : void 0,
521
+ enabled: e.enabled ? e.enabled() : !0
522
+ }));
523
+ }
524
+ function T(t, e) {
525
+ const n = (x[t] || []).find((o) => o.id === e);
526
+ if (!n || n.enabled && !n.enabled()) return null;
527
+ const i = n.prefilled ? k(w(n.prefilled)) : void 0, s = (n.workflow || "").trim();
528
+ if (s === "edc_form_fill" && i && typeof i == "object") {
529
+ const o = i;
530
+ if (!o.sub_project_id && !o.subProjectId) return null;
531
+ }
532
+ if (s === "edc_crf_design" && i && typeof i == "object") {
533
+ const o = i;
534
+ if (!o.sub_project_id && !o.subProjectId) return null;
535
+ }
536
+ return {
537
+ workflow: n.workflow,
538
+ title: n.label,
539
+ host: n.host,
540
+ prefilled: i,
541
+ hostContext: n.hostContext ? k(w(n.hostContext)) : void 0
542
+ };
543
+ }
544
+ function le() {
545
+ return f;
546
+ }
547
+ function he() {
548
+ return Object.entries(x).map(([t, e]) => ({ pageId: t, actions: e }));
549
+ }
550
+ function ce() {
551
+ if (L || !f) return null;
552
+ const t = x[f] || [];
553
+ for (const e of t) {
554
+ if (e.enabled && !e.enabled()) continue;
555
+ const n = e.hint ? w(e.hint) : `可使用「${e.label}」`;
556
+ return { pageId: f, actionId: e.id, hint: n };
557
+ }
558
+ return null;
559
+ }
560
+ let r = {}, a = null, b = "", E = "", m = null;
561
+ function ue() {
562
+ const t = (r.agentOrigin || "").replace(/\/$/, "");
563
+ if (!t)
564
+ throw new Error(
565
+ "Agent 前端地址未配置:请在 init({ agentOrigin }) 传入,或由宿主 BFF bootstrap 返回 agent_web_origin"
566
+ );
567
+ return t;
568
+ }
569
+ function z(t) {
570
+ var e;
571
+ t.agent_web_origin && (r.agentOrigin = t.agent_web_origin.replace(/\/$/, "")), t.agent_token && (b = t.agent_token), t.refresh_token && (E = t.refresh_token), (e = r.onBootstrap) == null || e.call(r, t);
572
+ }
573
+ function M() {
574
+ return d(this, null, function* () {
575
+ if (r.getToken)
576
+ return b = yield r.getToken(), b;
577
+ if (b) return b;
578
+ if (r.bootstrap) {
579
+ const t = yield r.bootstrap();
580
+ if (z(t), b) return b;
581
+ }
582
+ throw new Error("未连接 Agent:请配置 bootstrap 或 getToken");
583
+ });
584
+ }
585
+ function v() {
586
+ if (r.agentOrigin)
587
+ try {
588
+ y().updateLauncherHint(ce());
589
+ } catch (t) {
590
+ }
591
+ }
592
+ function y() {
593
+ const t = ue();
594
+ return a || (a = new ee(t, fe, {
595
+ onHintUse: (e, n) => {
596
+ const i = T(e, n);
597
+ i && O(i);
598
+ },
599
+ onHintDismiss: () => {
600
+ re(), v();
601
+ }
602
+ }), r.showHub !== !1 && a.mountHub()), a;
603
+ }
604
+ function de() {
605
+ if (!(!a || !r.agentOrigin)) {
606
+ for (const { pageId: t, actions: e } of he())
607
+ a.sendToHub({
608
+ channel: "agent-host",
609
+ type: "register-page",
610
+ pageId: t,
611
+ actions: j(e)
612
+ });
613
+ a.sendToHub({
614
+ channel: "agent-host",
615
+ type: "set-page",
616
+ pageId: le()
617
+ }), v();
618
+ }
619
+ }
620
+ function fe(t) {
621
+ var e;
622
+ if (t.type === "ready" && t.surface === "hub" && M().then((n) => {
623
+ a == null || a.sendToHub({
624
+ channel: "agent-host",
625
+ type: "init",
626
+ token: n,
627
+ refreshToken: E || void 0
628
+ }), de();
629
+ }).catch(() => {
630
+ }), t.type === "started" && (a == null || a.closeWidget(), a == null || a.openHubPanel(), a == null || a.sendToHub({
631
+ channel: "agent-host",
632
+ type: "task-started",
633
+ taskId: t.taskId,
634
+ sessionId: t.sessionId,
635
+ workflow: t.workflow,
636
+ label: t.label || t.workflow,
637
+ formValues: t.formValues,
638
+ prefilled: t.prefilled,
639
+ hostContext: t.hostContext,
640
+ hubContinue: t.hubContinue
641
+ })), t.type === "complete") {
642
+ if (t.status === "success") {
643
+ const n = t.hostContext && t.hostContext.refresh;
644
+ se(n);
645
+ }
646
+ (e = r.onTaskComplete) == null || e.call(r, {
647
+ taskId: t.taskId,
648
+ status: t.status,
649
+ hostContext: t.hostContext
650
+ }), a == null || a.closeWidget();
651
+ }
652
+ if (t.type === "close" && (a == null || a.closeWidget()), t.type === "open-widget" && O(t.payload), t.type === "open-action") {
653
+ const n = T(t.pageId, t.actionId);
654
+ n && O(n);
655
+ }
656
+ t.type === "hub-stats" && (t.running, a == null || a.updateLauncherStats(t.running, t.waiting), v());
657
+ }
658
+ function O(t) {
659
+ return d(this, null, function* () {
660
+ yield C(), r.prepareOpenWidget && (yield r.prepareOpenWidget());
661
+ const e = y(), n = yield M();
662
+ e.openWidget(
663
+ {
664
+ workflow: t.workflow,
665
+ title: t.title || t.label,
666
+ host: t.host || r.host,
667
+ prefilled: t.prefilled,
668
+ hostContext: t.hostContext,
669
+ sessionId: t.sessionId,
670
+ preferSandboxFiles: t.preferSandboxFiles
671
+ },
672
+ n,
673
+ E || void 0
674
+ );
675
+ });
676
+ }
677
+ function C() {
678
+ return d(this, null, function* () {
679
+ m && (yield m);
680
+ });
681
+ }
682
+ function ge(t) {
683
+ return d(this, null, function* () {
684
+ if (r = S({ showHub: !0 }, t), r.bootstrap) {
685
+ const e = yield r.bootstrap();
686
+ z(e);
687
+ }
688
+ if (r.agentOrigin && r.showHub !== !1 && typeof document != "undefined")
689
+ try {
690
+ y();
691
+ const e = yield M();
692
+ a == null || a.sendToHub({
693
+ channel: "agent-host",
694
+ type: "init",
695
+ token: e,
696
+ refreshToken: E || void 0
697
+ });
698
+ } catch (e) {
699
+ }
700
+ });
701
+ }
702
+ const pe = {
703
+ init(t) {
704
+ return d(this, null, function* () {
705
+ m && (yield m), m = ge(t).finally(() => {
706
+ m = null;
707
+ }), yield m;
708
+ });
709
+ },
710
+ getToken() {
711
+ return d(this, null, function* () {
712
+ return yield C(), M();
713
+ });
714
+ },
715
+ registerPage(i, s) {
716
+ return d(this, arguments, function* (t, e, n = {}) {
717
+ yield C(), ne(t, e, n), r.agentOrigin && y().sendToHub({
718
+ channel: "agent-host",
719
+ type: "register-page",
720
+ pageId: t,
721
+ actions: j(e)
722
+ }), v();
723
+ });
724
+ },
725
+ unregisterPage(t) {
726
+ return d(this, null, function* () {
727
+ yield C(), ie(t), r.agentOrigin && (y().sendToHub({ channel: "agent-host", type: "unregister-page", pageId: t }), y().sendToHub({ channel: "agent-host", type: "set-page", pageId: null })), v();
728
+ });
729
+ },
730
+ setCurrentPage(t) {
731
+ return d(this, null, function* () {
732
+ yield C(), ae(t), r.agentOrigin && y().sendToHub({ channel: "agent-host", type: "set-page", pageId: t }), v();
733
+ });
734
+ },
735
+ openAction(t, e) {
736
+ return d(this, null, function* () {
737
+ const n = T(t, e);
738
+ if (!n) throw new Error("当前操作不可用");
739
+ yield O(n);
740
+ });
741
+ },
742
+ openWorkflow(n) {
743
+ return d(this, arguments, function* (t, e = {}) {
744
+ yield O(B(S({
745
+ workflow: t
746
+ }, e), {
747
+ host: e.host || r.host,
748
+ title: e.title || e.label || t
749
+ }));
750
+ });
751
+ },
752
+ /**
753
+ * EDC AI 填表是否可打开:须 prefilled.sub_project_id + forms 列表(表单在 Widget 内选择)。
754
+ */
755
+ canOpen(t, e = {}) {
756
+ const n = (t || "").trim(), i = e.prefilled || {}, s = String(i.sub_project_id || i.subProjectId || "").trim();
757
+ if (n === "edc_form_fill") {
758
+ const o = i.forms;
759
+ return !!(s && Array.isArray(o) && o.length > 0);
760
+ }
761
+ return n === "edc_crf_design" ? !!s : !0;
762
+ },
763
+ destroy() {
764
+ a == null || a.destroy(), a = null, b = "", E = "", r = {}, m = null, oe();
765
+ }
766
+ };
767
+ typeof window != "undefined" && (window.AgentHost = pe);
768
+ export {
769
+ I as AGENT_HOST_MSG,
770
+ pe as AgentHost,
771
+ K as isAgentHostMessage
772
+ };
package/index.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ export type {
2
+ BootstrapResult,
3
+ HostInitOptions,
4
+ HostPageAction,
5
+ OpenWorkflowOptions,
6
+ } from './src/types'
7
+ export type { WidgetPayload, SerializedPageAction } from './src/messages'
8
+ export { AgentHost, AGENT_HOST_MSG, isAgentHostMessage } from './src/index'
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@_daniel_jiang/host-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Agent 平台宿主嵌入 SDK(iframe Hub/Widget,框架无关)",
5
+ "type": "module",
6
+ "main": "./dist/agent-host.js",
7
+ "module": "./dist/agent-host.mjs",
8
+ "types": "./index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/agent-host.mjs",
12
+ "require": "./dist/agent-host.js",
13
+ "default": "./dist/agent-host.mjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist/agent-host.js",
18
+ "dist/agent-host.mjs",
19
+ "index.d.ts",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "vite build"
24
+ },
25
+ "sideEffects": false,
26
+ "license": "MIT",
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org/"
30
+ }
31
+ }