@ripperts/poco-agent-embed 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 +123 -0
- package/index.d.ts +45 -0
- package/index.js +422 -0
- package/index.mjs +6 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Poco Agent Embed
|
|
2
|
+
|
|
3
|
+
将 Poco Agent 的对话、历史消息和产物预览嵌入第三方系统。SDK 不依赖 Vue 或 React,支持 ESM、CommonJS 和浏览器 Script 标签接入。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @ripperts/poco-agent-embed
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
SDK 需要在浏览器环境中挂载。第三方系统必须通过 `getAccessToken` 返回当前用户有效的 Poco 登录 token。
|
|
12
|
+
|
|
13
|
+
## Vue 2
|
|
14
|
+
|
|
15
|
+
```vue
|
|
16
|
+
<template>
|
|
17
|
+
<div ref="agentChat" style="height: 640px"></div>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
import { mountAgentEmbed } from "@ripperts/poco-agent-embed";
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
props: {
|
|
25
|
+
getAccessToken: {
|
|
26
|
+
type: Function,
|
|
27
|
+
required: true,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
data() {
|
|
31
|
+
return {
|
|
32
|
+
agentEmbedController: null,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
mounted() {
|
|
36
|
+
this.agentEmbedController = mountAgentEmbed({
|
|
37
|
+
baseUrl: "https://poco.example.com",
|
|
38
|
+
agentId: "<agent-id>",
|
|
39
|
+
target: this.$refs.agentChat,
|
|
40
|
+
getAccessToken: () => this.getAccessToken(),
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
beforeDestroy() {
|
|
44
|
+
if (this.agentEmbedController) {
|
|
45
|
+
this.agentEmbedController.destroy();
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
</script>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Vue 3
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<template>
|
|
56
|
+
<div ref="agentChat" style="height: 640px"></div>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup>
|
|
60
|
+
import { onBeforeUnmount, onMounted, ref } from "vue";
|
|
61
|
+
import { mountAgentEmbed } from "@ripperts/poco-agent-embed";
|
|
62
|
+
|
|
63
|
+
const props = defineProps({
|
|
64
|
+
getAccessToken: {
|
|
65
|
+
type: Function,
|
|
66
|
+
required: true,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
const agentChat = ref(null);
|
|
70
|
+
let agentEmbedController;
|
|
71
|
+
|
|
72
|
+
onMounted(() => {
|
|
73
|
+
agentEmbedController = mountAgentEmbed({
|
|
74
|
+
baseUrl: "https://poco.example.com",
|
|
75
|
+
agentId: "<agent-id>",
|
|
76
|
+
target: agentChat.value,
|
|
77
|
+
getAccessToken: () => props.getAccessToken(),
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
onBeforeUnmount(() => {
|
|
82
|
+
if (agentEmbedController) {
|
|
83
|
+
agentEmbedController.destroy();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
</script>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## React
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
import { useEffect, useRef } from "react";
|
|
93
|
+
import { mountAgentEmbed } from "@ripperts/poco-agent-embed";
|
|
94
|
+
|
|
95
|
+
interface AgentChatProps {
|
|
96
|
+
getAccessToken: () => string | Promise<string>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function AgentChat({ getAccessToken }: AgentChatProps) {
|
|
100
|
+
const targetRef = useRef<HTMLDivElement>(null);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!targetRef.current) return;
|
|
104
|
+
|
|
105
|
+
const controller = mountAgentEmbed({
|
|
106
|
+
baseUrl: "https://poco.example.com",
|
|
107
|
+
agentId: "<agent-id>",
|
|
108
|
+
target: targetRef.current,
|
|
109
|
+
getAccessToken,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return () => controller.destroy();
|
|
113
|
+
}, [getAccessToken]);
|
|
114
|
+
|
|
115
|
+
return <div ref={targetRef} style={{ height: 640 }} />;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 浏览器支持
|
|
120
|
+
|
|
121
|
+
支持现代 Chrome、Edge、Firefox 和 Safari。Vue 2 项目可以正常接入,但不支持 Internet Explorer 11。
|
|
122
|
+
|
|
123
|
+
完整参数和安全配置请参阅 [Agent 外部嵌入文档](https://ai-agent.fsxgt.cn/docs/external-integration/agent-embed)。
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type AgentEmbedTheme = "light" | "dark" | "system";
|
|
2
|
+
export type AgentEmbedPosition =
|
|
3
|
+
| "bottom-right"
|
|
4
|
+
| "bottom-left"
|
|
5
|
+
| "top-right"
|
|
6
|
+
| "top-left";
|
|
7
|
+
|
|
8
|
+
export interface AgentEmbedOptions {
|
|
9
|
+
agentId: string;
|
|
10
|
+
getAccessToken: () => string | Promise<string>;
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
target?: string | HTMLElement;
|
|
13
|
+
language?: string;
|
|
14
|
+
theme?: AgentEmbedTheme;
|
|
15
|
+
position?: AgentEmbedPosition;
|
|
16
|
+
defaultOpen?: boolean;
|
|
17
|
+
draggable?: boolean;
|
|
18
|
+
zIndex?: number;
|
|
19
|
+
openIcon?: string | Node | (() => Node);
|
|
20
|
+
closeIcon?: string | Node | (() => Node);
|
|
21
|
+
ariaLabel?: string;
|
|
22
|
+
onError?: (error: Error) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AgentEmbedController {
|
|
26
|
+
open(): void;
|
|
27
|
+
close(): void;
|
|
28
|
+
toggle(): void;
|
|
29
|
+
destroy(): void;
|
|
30
|
+
isOpen(): boolean;
|
|
31
|
+
iframe: HTMLIFrameElement;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function mountAgentEmbed(
|
|
35
|
+
options: AgentEmbedOptions,
|
|
36
|
+
): AgentEmbedController;
|
|
37
|
+
|
|
38
|
+
export const mount: typeof mountAgentEmbed;
|
|
39
|
+
|
|
40
|
+
declare const PocoAgentEmbed: {
|
|
41
|
+
mount: typeof mountAgentEmbed;
|
|
42
|
+
mountAgentEmbed: typeof mountAgentEmbed;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default PocoAgentEmbed;
|
package/index.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
var api = factory(
|
|
3
|
+
typeof document !== "undefined" ? document.currentScript : null,
|
|
4
|
+
);
|
|
5
|
+
if (typeof module === "object" && module.exports) {
|
|
6
|
+
module.exports = api;
|
|
7
|
+
}
|
|
8
|
+
if (root) {
|
|
9
|
+
root.PocoAgentEmbed = api;
|
|
10
|
+
}
|
|
11
|
+
})(
|
|
12
|
+
typeof globalThis !== "undefined" ? globalThis : this,
|
|
13
|
+
function (sourceScript) {
|
|
14
|
+
"use strict";
|
|
15
|
+
|
|
16
|
+
var DEFAULT_Z_INDEX = 2147483000;
|
|
17
|
+
var MESSAGE_READY = "poco-agent-embed:ready";
|
|
18
|
+
var MESSAGE_REQUEST_AUTH = "poco-agent-embed:request-auth";
|
|
19
|
+
var MESSAGE_AUTHENTICATE = "poco-agent-embed:authenticate";
|
|
20
|
+
var MESSAGE_ERROR = "poco-agent-embed:error";
|
|
21
|
+
|
|
22
|
+
function trimTrailingSlash(value) {
|
|
23
|
+
return String(value || "").replace(/\/+$/, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function inferBaseUrl() {
|
|
27
|
+
if (!sourceScript || !sourceScript.src) return "";
|
|
28
|
+
try {
|
|
29
|
+
return new URL(sourceScript.src, document.baseURI).origin;
|
|
30
|
+
} catch {
|
|
31
|
+
return "";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createChannel() {
|
|
36
|
+
if (
|
|
37
|
+
globalThis.crypto &&
|
|
38
|
+
typeof globalThis.crypto.randomUUID === "function"
|
|
39
|
+
) {
|
|
40
|
+
return globalThis.crypto.randomUUID();
|
|
41
|
+
}
|
|
42
|
+
return "poco-" + Date.now() + "-" + Math.random().toString(36).slice(2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveTarget(target) {
|
|
46
|
+
if (!target) return null;
|
|
47
|
+
if (typeof target === "string") return document.querySelector(target);
|
|
48
|
+
return target instanceof HTMLElement ? target : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createDefaultIcon(isClose) {
|
|
52
|
+
var span = document.createElement("span");
|
|
53
|
+
span.setAttribute("aria-hidden", "true");
|
|
54
|
+
span.style.cssText =
|
|
55
|
+
"display:flex;align-items:center;justify-content:center;width:22px;height:22px";
|
|
56
|
+
span.textContent = isClose ? "×" : "✦";
|
|
57
|
+
span.style.fontSize = isClose ? "26px" : "20px";
|
|
58
|
+
span.style.lineHeight = "1";
|
|
59
|
+
return span;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createIcon(value, isClose) {
|
|
63
|
+
if (typeof value === "function") {
|
|
64
|
+
var generated = value();
|
|
65
|
+
if (generated instanceof Node) return generated;
|
|
66
|
+
}
|
|
67
|
+
if (value instanceof Node) return value.cloneNode(true);
|
|
68
|
+
if (typeof value === "string" && value.trim()) {
|
|
69
|
+
var image = document.createElement("img");
|
|
70
|
+
image.src = value.trim();
|
|
71
|
+
image.alt = "";
|
|
72
|
+
image.style.cssText =
|
|
73
|
+
"display:block;width:22px;height:22px;object-fit:contain";
|
|
74
|
+
return image;
|
|
75
|
+
}
|
|
76
|
+
return createDefaultIcon(isClose);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function mountAgentEmbed(options) {
|
|
80
|
+
if (!options || typeof options !== "object") {
|
|
81
|
+
throw new TypeError("Poco Agent embed options are required");
|
|
82
|
+
}
|
|
83
|
+
var agentId = String(options.agentId || "").trim();
|
|
84
|
+
if (!agentId) throw new TypeError("agentId is required");
|
|
85
|
+
if (typeof options.getAccessToken !== "function") {
|
|
86
|
+
throw new TypeError("getAccessToken must be a function");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
var baseUrl = trimTrailingSlash(options.baseUrl || inferBaseUrl());
|
|
90
|
+
if (!baseUrl) {
|
|
91
|
+
throw new TypeError(
|
|
92
|
+
"baseUrl is required when the SDK URL cannot be inferred",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
var pocoOrigin = new URL(baseUrl).origin;
|
|
96
|
+
var target = resolveTarget(options.target);
|
|
97
|
+
if (options.target && !target)
|
|
98
|
+
throw new TypeError("target was not found");
|
|
99
|
+
var channel = createChannel();
|
|
100
|
+
var requestedLanguage = String(
|
|
101
|
+
options.language || document.documentElement.lang || "zh",
|
|
102
|
+
)
|
|
103
|
+
.trim()
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.split("-")[0];
|
|
106
|
+
var language = ["en", "zh"].includes(requestedLanguage)
|
|
107
|
+
? requestedLanguage
|
|
108
|
+
: "en";
|
|
109
|
+
var theme = ["light", "dark", "system"].includes(options.theme)
|
|
110
|
+
? options.theme
|
|
111
|
+
: "system";
|
|
112
|
+
var query = new URLSearchParams({ channel: channel, theme: theme });
|
|
113
|
+
var iframe = document.createElement("iframe");
|
|
114
|
+
iframe.src =
|
|
115
|
+
baseUrl +
|
|
116
|
+
"/" +
|
|
117
|
+
encodeURIComponent(language || "zh") +
|
|
118
|
+
"/embed/agents/" +
|
|
119
|
+
encodeURIComponent(agentId) +
|
|
120
|
+
"?" +
|
|
121
|
+
query.toString();
|
|
122
|
+
iframe.title = options.ariaLabel || "AI Agent";
|
|
123
|
+
iframe.setAttribute(
|
|
124
|
+
"sandbox",
|
|
125
|
+
"allow-downloads allow-forms allow-popups allow-same-origin allow-scripts",
|
|
126
|
+
);
|
|
127
|
+
iframe.setAttribute("allow", "clipboard-write");
|
|
128
|
+
iframe.style.cssText =
|
|
129
|
+
"display:block;width:100%;height:100%;border:0;background:transparent";
|
|
130
|
+
|
|
131
|
+
var destroyed = false;
|
|
132
|
+
var opened = Boolean(target || options.defaultOpen);
|
|
133
|
+
var container = document.createElement("div");
|
|
134
|
+
var button = null;
|
|
135
|
+
var updateViewportPosition = null;
|
|
136
|
+
var openIcon = createIcon(options.openIcon, false);
|
|
137
|
+
var closeIcon = createIcon(options.closeIcon, true);
|
|
138
|
+
|
|
139
|
+
if (target) {
|
|
140
|
+
container.style.cssText =
|
|
141
|
+
"position:relative;width:100%;height:100%;overflow:hidden";
|
|
142
|
+
container.appendChild(iframe);
|
|
143
|
+
target.appendChild(container);
|
|
144
|
+
} else {
|
|
145
|
+
container.style.cssText = [
|
|
146
|
+
"position:fixed",
|
|
147
|
+
"width:min(420px,calc(100vw - 24px))",
|
|
148
|
+
"height:min(720px,calc(100dvh - 96px))",
|
|
149
|
+
"min-height:min(360px,calc(100dvh - 96px))",
|
|
150
|
+
"overflow:hidden",
|
|
151
|
+
"border:1px solid color-mix(in srgb,CanvasText 25%,transparent)",
|
|
152
|
+
"border-radius:16px",
|
|
153
|
+
"background:Canvas",
|
|
154
|
+
"box-shadow:0 20px 60px color-mix(in srgb,CanvasText 20%,transparent)",
|
|
155
|
+
"transition:opacity .18s ease,transform .18s ease,visibility .18s ease",
|
|
156
|
+
"transform-origin:bottom right",
|
|
157
|
+
"z-index:" + String(options.zIndex || DEFAULT_Z_INDEX),
|
|
158
|
+
].join(";");
|
|
159
|
+
container.appendChild(iframe);
|
|
160
|
+
|
|
161
|
+
button = document.createElement("button");
|
|
162
|
+
button.type = "button";
|
|
163
|
+
button.setAttribute("aria-label", options.ariaLabel || "Open AI Agent");
|
|
164
|
+
button.style.cssText = [
|
|
165
|
+
"position:fixed",
|
|
166
|
+
"display:flex",
|
|
167
|
+
"align-items:center",
|
|
168
|
+
"justify-content:center",
|
|
169
|
+
"width:52px",
|
|
170
|
+
"height:52px",
|
|
171
|
+
"padding:0",
|
|
172
|
+
"border:0",
|
|
173
|
+
"border-radius:999px",
|
|
174
|
+
"background:var(--poco-agent-embed-button-background,CanvasText)",
|
|
175
|
+
"color:var(--poco-agent-embed-button-foreground,Canvas)",
|
|
176
|
+
"box-shadow:0 10px 30px color-mix(in srgb,CanvasText 25%,transparent)",
|
|
177
|
+
"cursor:pointer",
|
|
178
|
+
"touch-action:none",
|
|
179
|
+
"z-index:" + String((options.zIndex || DEFAULT_Z_INDEX) + 1),
|
|
180
|
+
].join(";");
|
|
181
|
+
|
|
182
|
+
var position = options.position || "bottom-right";
|
|
183
|
+
var vertical = position.indexOf("top") === 0 ? "top" : "bottom";
|
|
184
|
+
var horizontal = position.indexOf("left") > -1 ? "left" : "right";
|
|
185
|
+
button.style[vertical] = "20px";
|
|
186
|
+
button.style[horizontal] = "20px";
|
|
187
|
+
container.style[vertical] = "84px";
|
|
188
|
+
container.style[horizontal] = "20px";
|
|
189
|
+
container.style.transformOrigin = vertical + " " + horizontal;
|
|
190
|
+
|
|
191
|
+
function renderButtonIcon() {
|
|
192
|
+
button.replaceChildren(opened ? closeIcon : openIcon);
|
|
193
|
+
button.setAttribute("aria-expanded", opened ? "true" : "false");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderOpenState() {
|
|
197
|
+
container.style.opacity = opened ? "1" : "0";
|
|
198
|
+
container.style.visibility = opened ? "visible" : "hidden";
|
|
199
|
+
container.style.pointerEvents = opened ? "auto" : "none";
|
|
200
|
+
container.style.transform = opened ? "scale(1)" : "scale(.96)";
|
|
201
|
+
renderButtonIcon();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function anchorContainerToButton() {
|
|
205
|
+
var rect = button.getBoundingClientRect();
|
|
206
|
+
var useLeft = rect.left + rect.width / 2 < window.innerWidth / 2;
|
|
207
|
+
var popupWidth = Math.min(420, Math.max(0, window.innerWidth - 24));
|
|
208
|
+
var preferredLeft = useLeft ? rect.left : rect.right - popupWidth;
|
|
209
|
+
var popupLeft = Math.max(
|
|
210
|
+
8,
|
|
211
|
+
Math.min(window.innerWidth - popupWidth - 8, preferredLeft),
|
|
212
|
+
);
|
|
213
|
+
var availableAbove = Math.max(0, rect.top - 20);
|
|
214
|
+
var availableBelow = Math.max(
|
|
215
|
+
0,
|
|
216
|
+
window.innerHeight - rect.bottom - 20,
|
|
217
|
+
);
|
|
218
|
+
var useTop = availableBelow >= availableAbove;
|
|
219
|
+
var availableHeight = useTop ? availableBelow : availableAbove;
|
|
220
|
+
var popupHeight = Math.max(0, Math.min(720, availableHeight));
|
|
221
|
+
container.style.left = popupLeft + "px";
|
|
222
|
+
container.style.right = "auto";
|
|
223
|
+
container.style.top = useTop ? rect.bottom + 12 + "px" : "auto";
|
|
224
|
+
container.style.bottom = useTop
|
|
225
|
+
? "auto"
|
|
226
|
+
: Math.max(8, window.innerHeight - rect.top + 12) + "px";
|
|
227
|
+
container.style.height = popupHeight + "px";
|
|
228
|
+
container.style.minHeight = Math.min(360, popupHeight) + "px";
|
|
229
|
+
container.style.transformOrigin =
|
|
230
|
+
(useTop ? "top" : "bottom") + " " + (useLeft ? "left" : "right");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
updateViewportPosition = function () {
|
|
234
|
+
if (destroyed) return;
|
|
235
|
+
dragState = null;
|
|
236
|
+
var rect = button.getBoundingClientRect();
|
|
237
|
+
var left = Math.max(
|
|
238
|
+
8,
|
|
239
|
+
Math.min(window.innerWidth - rect.width - 8, rect.left),
|
|
240
|
+
);
|
|
241
|
+
var top = Math.max(
|
|
242
|
+
8,
|
|
243
|
+
Math.min(window.innerHeight - rect.height - 8, rect.top),
|
|
244
|
+
);
|
|
245
|
+
button.style.left = left + "px";
|
|
246
|
+
button.style.top = top + "px";
|
|
247
|
+
button.style.right = "auto";
|
|
248
|
+
button.style.bottom = "auto";
|
|
249
|
+
anchorContainerToButton();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
var dragState = null;
|
|
253
|
+
var suppressClickUntil = 0;
|
|
254
|
+
button.addEventListener("pointerdown", function (event) {
|
|
255
|
+
if (options.draggable === false) return;
|
|
256
|
+
var rect = button.getBoundingClientRect();
|
|
257
|
+
dragState = {
|
|
258
|
+
pointerId: event.pointerId,
|
|
259
|
+
startX: event.clientX,
|
|
260
|
+
startY: event.clientY,
|
|
261
|
+
left: rect.left,
|
|
262
|
+
top: rect.top,
|
|
263
|
+
moved: false,
|
|
264
|
+
};
|
|
265
|
+
button.setPointerCapture(event.pointerId);
|
|
266
|
+
});
|
|
267
|
+
button.addEventListener("pointermove", function (event) {
|
|
268
|
+
if (!dragState || dragState.pointerId !== event.pointerId) return;
|
|
269
|
+
var deltaX = event.clientX - dragState.startX;
|
|
270
|
+
var deltaY = event.clientY - dragState.startY;
|
|
271
|
+
if (Math.abs(deltaX) + Math.abs(deltaY) > 4) dragState.moved = true;
|
|
272
|
+
if (!dragState.moved) return;
|
|
273
|
+
var left = Math.max(
|
|
274
|
+
8,
|
|
275
|
+
Math.min(window.innerWidth - 60, dragState.left + deltaX),
|
|
276
|
+
);
|
|
277
|
+
var top = Math.max(
|
|
278
|
+
8,
|
|
279
|
+
Math.min(window.innerHeight - 60, dragState.top + deltaY),
|
|
280
|
+
);
|
|
281
|
+
button.style.left = left + "px";
|
|
282
|
+
button.style.top = top + "px";
|
|
283
|
+
button.style.right = "auto";
|
|
284
|
+
button.style.bottom = "auto";
|
|
285
|
+
});
|
|
286
|
+
button.addEventListener("pointerup", function (event) {
|
|
287
|
+
if (!dragState || dragState.pointerId !== event.pointerId) return;
|
|
288
|
+
var moved = dragState.moved;
|
|
289
|
+
dragState = null;
|
|
290
|
+
button.releasePointerCapture(event.pointerId);
|
|
291
|
+
if (moved) {
|
|
292
|
+
suppressClickUntil = Date.now() + 100;
|
|
293
|
+
anchorContainerToButton();
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
button.addEventListener("pointercancel", function () {
|
|
297
|
+
dragState = null;
|
|
298
|
+
});
|
|
299
|
+
button.addEventListener("click", function () {
|
|
300
|
+
if (Date.now() < suppressClickUntil) return;
|
|
301
|
+
opened = !opened;
|
|
302
|
+
renderOpenState();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
document.body.appendChild(container);
|
|
306
|
+
document.body.appendChild(button);
|
|
307
|
+
window.addEventListener("resize", updateViewportPosition);
|
|
308
|
+
window.addEventListener("orientationchange", updateViewportPosition);
|
|
309
|
+
if (window.visualViewport) {
|
|
310
|
+
window.visualViewport.addEventListener(
|
|
311
|
+
"resize",
|
|
312
|
+
updateViewportPosition,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
updateViewportPosition();
|
|
316
|
+
renderOpenState();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function authenticate() {
|
|
320
|
+
try {
|
|
321
|
+
var token = await options.getAccessToken();
|
|
322
|
+
token = String(token || "").trim();
|
|
323
|
+
if (!token) throw new Error("getAccessToken returned an empty token");
|
|
324
|
+
if (destroyed || !iframe.contentWindow) return;
|
|
325
|
+
iframe.contentWindow.postMessage(
|
|
326
|
+
{
|
|
327
|
+
type: MESSAGE_AUTHENTICATE,
|
|
328
|
+
channel: channel,
|
|
329
|
+
accessToken: token,
|
|
330
|
+
},
|
|
331
|
+
pocoOrigin,
|
|
332
|
+
);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
var normalized =
|
|
335
|
+
error instanceof Error ? error : new Error(String(error));
|
|
336
|
+
if (typeof options.onError === "function")
|
|
337
|
+
options.onError(normalized);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function handleMessage(event) {
|
|
342
|
+
if (destroyed || event.source !== iframe.contentWindow) return;
|
|
343
|
+
if (
|
|
344
|
+
event.origin !== pocoOrigin ||
|
|
345
|
+
!event.data ||
|
|
346
|
+
typeof event.data !== "object"
|
|
347
|
+
)
|
|
348
|
+
return;
|
|
349
|
+
if (event.data.channel !== channel) return;
|
|
350
|
+
if (
|
|
351
|
+
event.data.type === MESSAGE_READY ||
|
|
352
|
+
event.data.type === MESSAGE_REQUEST_AUTH
|
|
353
|
+
) {
|
|
354
|
+
authenticate();
|
|
355
|
+
} else if (
|
|
356
|
+
event.data.type === MESSAGE_ERROR &&
|
|
357
|
+
typeof options.onError === "function"
|
|
358
|
+
) {
|
|
359
|
+
options.onError(
|
|
360
|
+
new Error(String(event.data.message || "Agent embed failed")),
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
window.addEventListener("message", handleMessage);
|
|
366
|
+
|
|
367
|
+
function setOpened(next) {
|
|
368
|
+
if (destroyed || target) return;
|
|
369
|
+
opened = next;
|
|
370
|
+
container.style.opacity = opened ? "1" : "0";
|
|
371
|
+
container.style.visibility = opened ? "visible" : "hidden";
|
|
372
|
+
container.style.pointerEvents = opened ? "auto" : "none";
|
|
373
|
+
container.style.transform = opened ? "scale(1)" : "scale(.96)";
|
|
374
|
+
if (button) {
|
|
375
|
+
button.replaceChildren(opened ? closeIcon : openIcon);
|
|
376
|
+
button.setAttribute("aria-expanded", opened ? "true" : "false");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
iframe: iframe,
|
|
382
|
+
open: function () {
|
|
383
|
+
setOpened(true);
|
|
384
|
+
},
|
|
385
|
+
close: function () {
|
|
386
|
+
setOpened(false);
|
|
387
|
+
},
|
|
388
|
+
toggle: function () {
|
|
389
|
+
setOpened(!opened);
|
|
390
|
+
},
|
|
391
|
+
isOpen: function () {
|
|
392
|
+
return target ? true : opened;
|
|
393
|
+
},
|
|
394
|
+
destroy: function () {
|
|
395
|
+
if (destroyed) return;
|
|
396
|
+
destroyed = true;
|
|
397
|
+
window.removeEventListener("message", handleMessage);
|
|
398
|
+
if (updateViewportPosition) {
|
|
399
|
+
window.removeEventListener("resize", updateViewportPosition);
|
|
400
|
+
window.removeEventListener(
|
|
401
|
+
"orientationchange",
|
|
402
|
+
updateViewportPosition,
|
|
403
|
+
);
|
|
404
|
+
if (window.visualViewport) {
|
|
405
|
+
window.visualViewport.removeEventListener(
|
|
406
|
+
"resize",
|
|
407
|
+
updateViewportPosition,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
container.remove();
|
|
412
|
+
if (button) button.remove();
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
mount: mountAgentEmbed,
|
|
419
|
+
mountAgentEmbed: mountAgentEmbed,
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
);
|
package/index.mjs
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ripperts/poco-agent-embed",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-dependency SDK for embedding a Poco Agent conversation",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "index.mjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"import": "./index.mjs",
|
|
12
|
+
"require": "./index.js",
|
|
13
|
+
"default": "./index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.js",
|
|
18
|
+
"index.mjs",
|
|
19
|
+
"index.d.ts",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/RipperTs/poco-ai-agent.git",
|
|
25
|
+
"directory": "frontend/packages/agent-embed"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/RipperTs/poco-ai-agent#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/RipperTs/poco-ai-agent/issues"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"registry": "https://registry.npmjs.org/"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"sideEffects": false
|
|
37
|
+
}
|