@super_studio/ecforce-ai-agent-react 1.1.1 → 1.2.0-canary.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/dist/components/ai-icon.mjs +8 -9
- package/dist/components/ai-icon.mjs.map +1 -1
- package/dist/components/chatbot-frame.d.mts +2 -2
- package/dist/components/chatbot-sheet.d.mts +2 -2
- package/dist/components/chatbot-sheet.mjs +2 -2
- package/dist/components/chatbot-sheet.mjs.map +1 -1
- package/dist/components/provider/chatbot-provider.d.mts +33 -0
- package/dist/components/provider/chatbot-provider.d.mts.map +1 -1
- package/dist/components/provider/chatbot-provider.mjs +31 -1
- package/dist/components/provider/chatbot-provider.mjs.map +1 -1
- package/dist/components/provider/use-chatbot-frame-handler.d.mts +9 -0
- package/dist/components/provider/use-chatbot-frame-handler.d.mts.map +1 -1
- package/dist/components/provider/use-chatbot-frame-handler.mjs +101 -0
- package/dist/components/provider/use-chatbot-frame-handler.mjs.map +1 -1
- package/dist/components/provider/use-chatbot-window-states.mjs +25 -0
- package/dist/components/provider/use-chatbot-window-states.mjs.map +1 -1
- package/dist/components/sheet.d.mts +2 -2
- package/dist/components/tooltip.d.mts +2 -2
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +2 -1
- package/dist/lib/client-tools.d.mts +75 -0
- package/dist/lib/client-tools.d.mts.map +1 -0
- package/dist/lib/client-tools.mjs +80 -0
- package/dist/lib/client-tools.mjs.map +1 -0
- package/package.json +10 -3
- package/src/components/ai-icon.tsx +14 -11
- package/src/components/chatbot-sheet.tsx +1 -1
- package/src/components/provider/chatbot-provider.tsx +38 -1
- package/src/components/provider/use-chatbot-frame-handler.tsx +165 -6
- package/src/components/provider/use-chatbot-window-states.tsx +25 -0
- package/src/index.ts +1 -0
- package/src/lib/client-tools.ts +137 -0
|
@@ -1,18 +1,17 @@
|
|
|
1
|
+
import { PROD_CHATBOT_URL } from "../lib/constants.mjs";
|
|
1
2
|
import { jsx } from "react/jsx-runtime";
|
|
2
3
|
|
|
3
4
|
//#region src/components/ai-icon.tsx
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
const AI_ICON_GIF_URL = new URL("/images/ai-icon.gif", PROD_CHATBOT_URL).toString();
|
|
6
|
+
function AiIcon({ width = 24, height = 24 }) {
|
|
7
|
+
return /* @__PURE__ */ jsx("img", {
|
|
8
|
+
src: AI_ICON_GIF_URL,
|
|
6
9
|
width,
|
|
7
10
|
height,
|
|
8
|
-
|
|
9
|
-
fill: "currentColor",
|
|
11
|
+
alt: "",
|
|
10
12
|
"aria-hidden": "true",
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
clipRule: "evenodd",
|
|
14
|
-
d: "M9.2 13.704s3.7-.812 4.325-3.74c.624-2.93.76-3.364 1.112-3.364.352 0 .42.309.5.618.08.309.51 2.276.613 2.745.102.47.783 2.93 3.712 3.616 2.93.686 3.338.697 3.338 1.12 0 .459-2.361.859-3.338 1.122-.976.263-3.042 1.007-3.587 3.49-.534 2.482-.681 3.489-1.238 3.489-.556 0-.545-.584-.613-.87-.068-.286-.34-1.819-.5-2.494-.147-.675-1.044-2.837-2.962-3.363-1.93-.527-3.962-.847-3.962-1.373s2.134-.824 2.6-.995ZM2.462 4.613s1.785-.39 2.08-1.798c.296-1.409.365-1.615.535-1.615.17 0 .205.149.239.298s.25 1.088.296 1.317c.045.229.375 1.409 1.785 1.74 1.41.333 1.603.333 1.603.54 0 .217-1.137.412-1.603.538-.466.126-1.467.48-1.729 1.683C5.407 8.507 5.338 9 5.078 9c-.262 0-.262-.286-.296-.424-.035-.137-.16-.882-.24-1.202-.079-.321-.5-1.363-1.432-1.615-.932-.252-1.91-.413-1.91-.665 0-.252 1.023-.4 1.25-.48h.012Z"
|
|
15
|
-
})
|
|
13
|
+
draggable: false,
|
|
14
|
+
style: { display: "block" }
|
|
16
15
|
});
|
|
17
16
|
}
|
|
18
17
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-icon.mjs","names":[],"sources":["../../src/components/ai-icon.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"ai-icon.mjs","names":[],"sources":["../../src/components/ai-icon.tsx"],"sourcesContent":["import { PROD_CHATBOT_URL } from \"../lib/constants\";\n\ntype AiIconProps = {\n width?: number;\n height?: number;\n};\n\nconst AI_ICON_GIF_URL = new URL(\n \"/images/ai-icon.gif\",\n PROD_CHATBOT_URL,\n).toString();\n\nexport function AiIcon({ width = 24, height = 24 }: AiIconProps) {\n return (\n <img\n src={AI_ICON_GIF_URL}\n width={width}\n height={height}\n alt=\"\"\n aria-hidden=\"true\"\n draggable={false}\n style={{ display: \"block\" }}\n />\n );\n}\n"],"mappings":";;;;AAOA,MAAM,kBAAkB,IAAI,IAC1B,uBACA,iBACD,CAAC,UAAU;AAEZ,SAAgB,OAAO,EAAE,QAAQ,IAAI,SAAS,MAAmB;AAC/D,QACE,oBAAC;EACC,KAAK;EACE;EACC;EACR,KAAI;EACJ,eAAY;EACZ,WAAW;EACX,OAAO,EAAE,SAAS,SAAS;GAC3B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region src/components/chatbot-frame.d.ts
|
|
4
4
|
type ChatbotFrameProps = {
|
|
@@ -37,7 +37,7 @@ declare function ChatbotFrame({
|
|
|
37
37
|
enabled,
|
|
38
38
|
url,
|
|
39
39
|
className
|
|
40
|
-
}: ChatbotFrameProps):
|
|
40
|
+
}: ChatbotFrameProps): react_jsx_runtime0.JSX.Element;
|
|
41
41
|
type SessionData = {
|
|
42
42
|
token: string;
|
|
43
43
|
expiresAt: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChatbotFrameProps } from "./chatbot-frame.mjs";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import * as
|
|
3
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
4
4
|
|
|
5
5
|
//#region src/components/chatbot-sheet.d.ts
|
|
6
6
|
type ChatbotSheetProps = ChatbotFrameProps & {
|
|
@@ -9,7 +9,7 @@ type ChatbotSheetProps = ChatbotFrameProps & {
|
|
|
9
9
|
declare function ChatbotSheet({
|
|
10
10
|
sheetStyle,
|
|
11
11
|
...props
|
|
12
|
-
}: ChatbotSheetProps):
|
|
12
|
+
}: ChatbotSheetProps): react_jsx_runtime1.JSX.Element;
|
|
13
13
|
//#endregion
|
|
14
14
|
export { ChatbotSheet };
|
|
15
15
|
//# sourceMappingURL=chatbot-sheet.d.mts.map
|
|
@@ -18,8 +18,8 @@ function ChatbotSheet(_ref) {
|
|
|
18
18
|
align: "end",
|
|
19
19
|
content: "AIに質問してみましょう",
|
|
20
20
|
trigger: /* @__PURE__ */ jsx(SheetTrigger, { children: /* @__PURE__ */ jsx(AiIcon, {
|
|
21
|
-
width:
|
|
22
|
-
height:
|
|
21
|
+
width: 24,
|
|
22
|
+
height: 24
|
|
23
23
|
}) })
|
|
24
24
|
}), /* @__PURE__ */ jsxs(SheetContent, {
|
|
25
25
|
style: sheetStyle,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatbot-sheet.mjs","names":[],"sources":["../../src/components/chatbot-sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport { AiIcon } from \"./ai-icon\";\nimport { ChatbotFrame, type ChatbotFrameProps } from \"./chatbot-frame\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetTitle,\n SheetTrigger,\n} from \"./sheet\";\nimport { Tooltip } from \"./tooltip\";\n\ntype ChatbotSheetProps = ChatbotFrameProps & {\n sheetStyle?: React.CSSProperties;\n};\n\nexport function ChatbotSheet({ sheetStyle, ...props }: ChatbotSheetProps) {\n return (\n <Sheet>\n <Tooltip\n side=\"top\"\n align=\"end\"\n content=\"AIに質問してみましょう\"\n trigger={\n <SheetTrigger>\n <AiIcon width={
|
|
1
|
+
{"version":3,"file":"chatbot-sheet.mjs","names":[],"sources":["../../src/components/chatbot-sheet.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport { AiIcon } from \"./ai-icon\";\nimport { ChatbotFrame, type ChatbotFrameProps } from \"./chatbot-frame\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetTitle,\n SheetTrigger,\n} from \"./sheet\";\nimport { Tooltip } from \"./tooltip\";\n\ntype ChatbotSheetProps = ChatbotFrameProps & {\n sheetStyle?: React.CSSProperties;\n};\n\nexport function ChatbotSheet({ sheetStyle, ...props }: ChatbotSheetProps) {\n return (\n <Sheet>\n <Tooltip\n side=\"top\"\n align=\"end\"\n content=\"AIに質問してみましょう\"\n trigger={\n <SheetTrigger>\n <AiIcon width={24} height={24} />\n </SheetTrigger>\n }\n />\n <SheetContent style={sheetStyle}>\n <SheetTitle>AIに質問してみましょう</SheetTitle>\n <SheetDescription>AIに質問してみましょう</SheetDescription>\n <ChatbotFrame {...props} />\n </SheetContent>\n </Sheet>\n );\n}\n"],"mappings":";;;;;;;;;;;;mBAkB+B;AAA/B,SAAgB,aAAa,MAA6C;KAA7C,EAAE,qBAAe;AAC5C,QACE,qBAAC,oBACC,oBAAC;EACC,MAAK;EACL,OAAM;EACN,SAAQ;EACR,SACE,oBAAC,0BACC,oBAAC;GAAO,OAAO;GAAI,QAAQ;IAAM,GACpB;GAEjB,EACF,qBAAC;EAAa,OAAO;;GACnB,oBAAC,wBAAW,iBAAyB;GACrC,oBAAC,8BAAiB,iBAA+B;GACjD,oBAAC,iCAAiB,OAAS;;GACd,IACT"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RegisteredClientTool } from "../../lib/client-tools.mjs";
|
|
1
2
|
import { InitProps, MCP, SendAppMessagePayload } from "./use-chatbot-frame-handler.mjs";
|
|
2
3
|
import React from "react";
|
|
3
4
|
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
@@ -28,6 +29,10 @@ type ChatbotContextType = {
|
|
|
28
29
|
setSessionToken: (sessionToken: string) => void;
|
|
29
30
|
/** アプリからメッセージを送信 */
|
|
30
31
|
sendAppMessage: (payload: SendAppMessagePayload) => void;
|
|
32
|
+
/** Client Toolを登録 */
|
|
33
|
+
registerClientTool: (tool: RegisteredClientTool) => void;
|
|
34
|
+
/** Client Toolを解除 */
|
|
35
|
+
unregisterClientTool: (name: string) => void;
|
|
31
36
|
/** チャットボットが準備できたか */
|
|
32
37
|
isReady: boolean;
|
|
33
38
|
/** チャットボットが初期化されたか */
|
|
@@ -38,6 +43,34 @@ type ChatbotContextType = {
|
|
|
38
43
|
*/
|
|
39
44
|
setIframeEl: (iframeEl: HTMLIFrameElement | null) => void;
|
|
40
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* `ChatbotProvider` が提供するチャットボット状態と操作 API を取得します。
|
|
48
|
+
*
|
|
49
|
+
* @returns チャットボットの表示状態、初期化 API、Client Tool の登録 API を含むコンテキスト値。
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* import { useChatbot } from "@super_studio/ecforce-ai-agent-react";
|
|
54
|
+
*
|
|
55
|
+
* export function OpenChatButton() {
|
|
56
|
+
* const { open, setOpen, sendAppMessage } = useChatbot();
|
|
57
|
+
*
|
|
58
|
+
* return (
|
|
59
|
+
* <button
|
|
60
|
+
* type="button"
|
|
61
|
+
* onClick={() => {
|
|
62
|
+
* setOpen(!open);
|
|
63
|
+
* sendAppMessage({
|
|
64
|
+
* message: "EC Forceの注文状況を確認したいです",
|
|
65
|
+
* });
|
|
66
|
+
* }}
|
|
67
|
+
* >
|
|
68
|
+
* Open chat
|
|
69
|
+
* </button>
|
|
70
|
+
* );
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
41
74
|
declare function useChatbot(): ChatbotContextType;
|
|
42
75
|
type ChatbotProviderProps = {
|
|
43
76
|
children: React.ReactNode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatbot-provider.d.mts","names":[],"sources":["../../../src/components/provider/chatbot-provider.tsx"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chatbot-provider.d.mts","names":[],"sources":["../../../src/components/provider/chatbot-provider.tsx"],"sourcesContent":[],"mappings":";;;;;;KAYK,kBAAA;;;EAAA;EAgBW,IAAA,EAAA,OAAA;EAEE;EAMU,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EAEC;EAWH,UAAA,EAAA,OAAA;EAAiB;EAmC3B,aAAU,EAAA,CAAA,QAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EAQrB;EAIW,YAAA,EAAA,OAAe;EAAG;EAAY,eAAA,EAAA,CAAA,UAAA,EAAA,OAAA,EAAA,GAAA,IAAA;EAAoB;EAAA,IAAA,EAAA,CAAA,KAAA,EApElD,SAoEkD,EAAA,GAAA,IAAA;;kBAlEhD;;;;;;4BAMU;;6BAEC;;;;;;;;;;;0BAWH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmCV,UAAA,CAAA,GAAU;KAQrB,oBAAA;YACO,KAAA,CAAM;;iBAGF,eAAA;;GAA8B,uBAAoB,kBAAA,CAAA,GAAA,CAAA"}
|
|
@@ -7,6 +7,34 @@ import { jsx } from "react/jsx-runtime";
|
|
|
7
7
|
|
|
8
8
|
//#region src/components/provider/chatbot-provider.tsx
|
|
9
9
|
const ChatbotContext = React.createContext(void 0);
|
|
10
|
+
/**
|
|
11
|
+
* `ChatbotProvider` が提供するチャットボット状態と操作 API を取得します。
|
|
12
|
+
*
|
|
13
|
+
* @returns チャットボットの表示状態、初期化 API、Client Tool の登録 API を含むコンテキスト値。
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* import { useChatbot } from "@super_studio/ecforce-ai-agent-react";
|
|
18
|
+
*
|
|
19
|
+
* export function OpenChatButton() {
|
|
20
|
+
* const { open, setOpen, sendAppMessage } = useChatbot();
|
|
21
|
+
*
|
|
22
|
+
* return (
|
|
23
|
+
* <button
|
|
24
|
+
* type="button"
|
|
25
|
+
* onClick={() => {
|
|
26
|
+
* setOpen(!open);
|
|
27
|
+
* sendAppMessage({
|
|
28
|
+
* message: "EC Forceの注文状況を確認したいです",
|
|
29
|
+
* });
|
|
30
|
+
* }}
|
|
31
|
+
* >
|
|
32
|
+
* Open chat
|
|
33
|
+
* </button>
|
|
34
|
+
* );
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
10
38
|
function useChatbot() {
|
|
11
39
|
const context = React.useContext(ChatbotContext);
|
|
12
40
|
if (!context) throw new Error("useChatbot must be used within a ChatbotProvider");
|
|
@@ -14,7 +42,7 @@ function useChatbot() {
|
|
|
14
42
|
}
|
|
15
43
|
function ChatbotProvider({ children }) {
|
|
16
44
|
const { hasOpened, open, isExpanded, isFullScreen, setOpen, setIsExpanded, setIsFullScreen } = useChatbotWindowStates();
|
|
17
|
-
const { setMcps, setAppName, setSessionToken, sendAppMessage, init, isReady, isInitialized, setIframeEl } = useChatbotFrameHandler({
|
|
45
|
+
const { setMcps, setAppName, setSessionToken, sendAppMessage, registerClientTool, unregisterClientTool, init, isReady, isInitialized, setIframeEl } = useChatbotFrameHandler({
|
|
18
46
|
setOpen,
|
|
19
47
|
setIsExpanded,
|
|
20
48
|
setIsFullScreen
|
|
@@ -29,6 +57,8 @@ function ChatbotProvider({ children }) {
|
|
|
29
57
|
setIsFullScreen,
|
|
30
58
|
setSessionToken,
|
|
31
59
|
sendAppMessage,
|
|
60
|
+
registerClientTool,
|
|
61
|
+
unregisterClientTool,
|
|
32
62
|
init,
|
|
33
63
|
setMcps,
|
|
34
64
|
setAppName,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chatbot-provider.mjs","names":[],"sources":["../../../src/components/provider/chatbot-provider.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport {\
|
|
1
|
+
{"version":3,"file":"chatbot-provider.mjs","names":[],"sources":["../../../src/components/provider/chatbot-provider.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport type { RegisteredClientTool } from \"../../lib/client-tools\";\nimport {\n useChatbotFrameHandler,\n type InitProps,\n type MCP,\n type SendAppMessagePayload,\n} from \"./use-chatbot-frame-handler\";\nimport { useChatbotWindowStates } from \"./use-chatbot-window-states\";\n\ntype ChatbotContextType = {\n /** 1回チャットボットを開いたら、固定でtrueになる */\n hasOpened: boolean;\n /** チャットボットを開いているか */\n open: boolean;\n /** チャットボットを開く */\n setOpen: (open: boolean) => void;\n /** チャットボットを展開しているか */\n isExpanded: boolean;\n /** チャットボットを展開する */\n setIsExpanded: (expanded: boolean) => void;\n /** チャットボットをフルスクリーンにしているか */\n isFullScreen: boolean;\n /** チャットボットをフルスクリーンにする */\n setIsFullScreen: (fullScreen: boolean) => void;\n /** チャットボットを初期化 */\n init: (props: InitProps) => void;\n /** MCPを設定 */\n setMcps: (mcps: MCP[]) => void;\n /** アプリ名を設定 */\n setAppName: (appName: string) => void;\n /** セッショントークンを設定 */\n setSessionToken: (sessionToken: string) => void;\n /** アプリからメッセージを送信 */\n sendAppMessage: (payload: SendAppMessagePayload) => void;\n /** Client Toolを登録 */\n registerClientTool: (tool: RegisteredClientTool) => void;\n /** Client Toolを解除 */\n unregisterClientTool: (name: string) => void;\n /** チャットボットが準備できたか */\n isReady: boolean;\n /** チャットボットが初期化されたか */\n isInitialized: boolean;\n /**\n * @internal\n * チャットボットのiframe要素を設定\n */\n setIframeEl: (iframeEl: HTMLIFrameElement | null) => void;\n};\n\nconst ChatbotContext = React.createContext<ChatbotContextType | undefined>(\n undefined,\n);\n\n/**\n * `ChatbotProvider` が提供するチャットボット状態と操作 API を取得します。\n *\n * @returns チャットボットの表示状態、初期化 API、Client Tool の登録 API を含むコンテキスト値。\n *\n * @example\n * ```tsx\n * import { useChatbot } from \"@super_studio/ecforce-ai-agent-react\";\n *\n * export function OpenChatButton() {\n * const { open, setOpen, sendAppMessage } = useChatbot();\n *\n * return (\n * <button\n * type=\"button\"\n * onClick={() => {\n * setOpen(!open);\n * sendAppMessage({\n * message: \"EC Forceの注文状況を確認したいです\",\n * });\n * }}\n * >\n * Open chat\n * </button>\n * );\n * }\n * ```\n */\nexport function useChatbot() {\n const context = React.useContext(ChatbotContext);\n if (!context) {\n throw new Error(\"useChatbot must be used within a ChatbotProvider\");\n }\n return context;\n}\n\ntype ChatbotProviderProps = {\n children: React.ReactNode;\n};\n\nexport function ChatbotProvider({ children }: ChatbotProviderProps) {\n const {\n hasOpened,\n open,\n isExpanded,\n isFullScreen,\n setOpen,\n setIsExpanded,\n setIsFullScreen,\n } = useChatbotWindowStates();\n const {\n setMcps,\n setAppName,\n setSessionToken,\n sendAppMessage,\n registerClientTool,\n unregisterClientTool,\n init,\n isReady,\n isInitialized,\n setIframeEl,\n } = useChatbotFrameHandler({\n setOpen,\n setIsExpanded,\n setIsFullScreen,\n });\n\n const value = {\n hasOpened,\n open,\n setOpen,\n isExpanded,\n setIsExpanded,\n isFullScreen,\n setIsFullScreen,\n setSessionToken,\n sendAppMessage,\n registerClientTool,\n unregisterClientTool,\n init,\n setMcps,\n setAppName,\n isReady,\n isInitialized,\n setIframeEl,\n };\n\n return (\n <ChatbotContext.Provider value={value}>{children}</ChatbotContext.Provider>\n );\n}\n"],"mappings":";;;;;;;;AAoDA,MAAM,iBAAiB,MAAM,cAC3B,OACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BD,SAAgB,aAAa;CAC3B,MAAM,UAAU,MAAM,WAAW,eAAe;AAChD,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,mDAAmD;AAErE,QAAO;;AAOT,SAAgB,gBAAgB,EAAE,YAAkC;CAClE,MAAM,EACJ,WACA,MACA,YACA,cACA,SACA,eACA,oBACE,wBAAwB;CAC5B,MAAM,EACJ,SACA,YACA,iBACA,gBACA,oBACA,sBACA,MACA,SACA,eACA,gBACE,uBAAuB;EACzB;EACA;EACA;EACD,CAAC;CAEF,MAAM,QAAQ;EACZ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD;AAED,QACE,oBAAC,eAAe;EAAgB;EAAQ;GAAmC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../../lib/client-tools.mjs";
|
|
1
2
|
import React from "react";
|
|
2
3
|
|
|
3
4
|
//#region src/components/provider/use-chatbot-frame-handler.d.ts
|
|
@@ -13,15 +14,23 @@ type InitProps = {
|
|
|
13
14
|
sessionToken?: string;
|
|
14
15
|
};
|
|
15
16
|
type AppMessageAttachment = {
|
|
17
|
+
/** 添付ファイルの表示名です。 */
|
|
16
18
|
fileName: string;
|
|
19
|
+
/** 添付ファイルの MIME type です。 */
|
|
17
20
|
mediaType: string;
|
|
21
|
+
/** 添付ファイルを取得できる URL です。 */
|
|
18
22
|
url: string;
|
|
19
23
|
};
|
|
20
24
|
type SendAppMessagePayload = {
|
|
25
|
+
/** チャット上に表示するタイトルです。 */
|
|
21
26
|
title?: string;
|
|
27
|
+
/** ユーザーに表示するメッセージ本文です。 */
|
|
22
28
|
message: string;
|
|
29
|
+
/** UI に表示せず、裏側のコンテキストとして渡すメッセージです。 */
|
|
23
30
|
hiddenMessage?: string;
|
|
31
|
+
/** 新規チャットを開始してから送るかを指定します。 */
|
|
24
32
|
startOnNewChat?: boolean;
|
|
33
|
+
/** メッセージと一緒に渡す添付ファイル一覧です。 */
|
|
25
34
|
attachments?: AppMessageAttachment[];
|
|
26
35
|
};
|
|
27
36
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chatbot-frame-handler.d.mts","names":[],"sources":["../../../src/components/provider/use-chatbot-frame-handler.tsx"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-chatbot-frame-handler.d.mts","names":[],"sources":["../../../src/components/provider/use-chatbot-frame-handler.tsx"],"sourcesContent":[],"mappings":";;;;;KAsBY,GAAA;;;YAGA;;KAGA,SAAA;SACH;;;;KAKG,oBAAA;;;;;;;;KASA,qBAAA;;;;;;;;;;gBAUI"}
|
|
@@ -4,11 +4,46 @@ import { _objectSpread2 } from "../../_virtual/_@oxc-project_runtime@0.103.0/hel
|
|
|
4
4
|
import React from "react";
|
|
5
5
|
|
|
6
6
|
//#region src/components/provider/use-chatbot-frame-handler.tsx
|
|
7
|
+
function serializeError(error) {
|
|
8
|
+
if (error instanceof Error) return error.message;
|
|
9
|
+
if (typeof error === "string") return error;
|
|
10
|
+
return "Unknown client tool error";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* iframe と親アプリ間のメッセージ送受信を管理し、チャットボット制御 API をまとめます。
|
|
14
|
+
*
|
|
15
|
+
* @returns iframe 初期化、メッセージ送信、Client Tool 登録に使うハンドラ一式を返します。
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* export function FrameBridge() {
|
|
20
|
+
* const [open, setOpen] = React.useState(false);
|
|
21
|
+
* const [isExpanded, setIsExpanded] = React.useState(false);
|
|
22
|
+
* const [isFullScreen, setIsFullScreen] = React.useState(false);
|
|
23
|
+
* const { setIframeEl, init, sendAppMessage, isReady } =
|
|
24
|
+
* useChatbotFrameHandler({
|
|
25
|
+
* setOpen,
|
|
26
|
+
* setIsExpanded,
|
|
27
|
+
* setIsFullScreen,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* React.useEffect(() => {
|
|
31
|
+
* if (isReady) {
|
|
32
|
+
* init({ appName: "EC Force" });
|
|
33
|
+
* sendAppMessage({ message: "在庫を確認したいです" });
|
|
34
|
+
* }
|
|
35
|
+
* }, [init, isReady, sendAppMessage]);
|
|
36
|
+
*
|
|
37
|
+
* return <iframe ref={setIframeEl} src="https://example.com/embed" />;
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
7
41
|
function useChatbotFrameHandler({ setOpen, setIsExpanded, setIsFullScreen }) {
|
|
8
42
|
const [iframeEl, setIframeEl] = React.useState(null);
|
|
9
43
|
const [isReady, setIsReady] = React.useState(false);
|
|
10
44
|
const [isInitialized, setIsInitialized] = React.useState(false);
|
|
11
45
|
const pendingAppMessageRef = React.useRef(null);
|
|
46
|
+
const clientToolRegistryRef = React.useRef(/* @__PURE__ */ new Map());
|
|
12
47
|
const postMessage = React.useCallback((message) => {
|
|
13
48
|
if (iframeEl === null || iframeEl === void 0 ? void 0 : iframeEl.contentWindow) {
|
|
14
49
|
if (process.env.NODE_ENV === "development") console.log("sending message to iframe", message);
|
|
@@ -61,6 +96,42 @@ function useChatbotFrameHandler({ setOpen, setIsExpanded, setIsFullScreen }) {
|
|
|
61
96
|
React.useEffect(() => {
|
|
62
97
|
flushPendingAppMessage();
|
|
63
98
|
}, [flushPendingAppMessage]);
|
|
99
|
+
const registerClientTool = React.useCallback((tool) => {
|
|
100
|
+
clientToolRegistryRef.current.set(tool.definition.name, tool);
|
|
101
|
+
if (!isReady || !isInitialized) return;
|
|
102
|
+
postMessage({
|
|
103
|
+
type: "registerClientTools",
|
|
104
|
+
tools: [tool.definition]
|
|
105
|
+
});
|
|
106
|
+
}, [
|
|
107
|
+
isInitialized,
|
|
108
|
+
isReady,
|
|
109
|
+
postMessage
|
|
110
|
+
]);
|
|
111
|
+
const unregisterClientTool = React.useCallback((name) => {
|
|
112
|
+
if (!clientToolRegistryRef.current.delete(name) || !isReady || !isInitialized) return;
|
|
113
|
+
postMessage({
|
|
114
|
+
type: "unregisterClientTools",
|
|
115
|
+
names: [name]
|
|
116
|
+
});
|
|
117
|
+
}, [
|
|
118
|
+
isInitialized,
|
|
119
|
+
isReady,
|
|
120
|
+
postMessage
|
|
121
|
+
]);
|
|
122
|
+
React.useEffect(() => {
|
|
123
|
+
if (!isReady || !isInitialized) return;
|
|
124
|
+
const tools = [...clientToolRegistryRef.current.values()].map((tool) => tool.definition);
|
|
125
|
+
if (tools.length === 0) return;
|
|
126
|
+
postMessage({
|
|
127
|
+
type: "registerClientTools",
|
|
128
|
+
tools
|
|
129
|
+
});
|
|
130
|
+
}, [
|
|
131
|
+
isInitialized,
|
|
132
|
+
isReady,
|
|
133
|
+
postMessage
|
|
134
|
+
]);
|
|
64
135
|
React.useEffect(() => {
|
|
65
136
|
const iframe = iframeEl;
|
|
66
137
|
if (!iframe) return;
|
|
@@ -93,6 +164,34 @@ function useChatbotFrameHandler({ setOpen, setIsExpanded, setIsFullScreen }) {
|
|
|
93
164
|
case "RELOAD_CHATBOT":
|
|
94
165
|
iframe.src = iframe.src;
|
|
95
166
|
break;
|
|
167
|
+
case "EXECUTE_CLIENT_TOOL": {
|
|
168
|
+
const { callId, name, args } = event.data;
|
|
169
|
+
const tool = clientToolRegistryRef.current.get(name);
|
|
170
|
+
if (!tool) {
|
|
171
|
+
postMessage({
|
|
172
|
+
type: "clientToolResult",
|
|
173
|
+
callId,
|
|
174
|
+
error: `Client tool not found: ${name}`
|
|
175
|
+
});
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
(async () => {
|
|
179
|
+
try {
|
|
180
|
+
postMessage({
|
|
181
|
+
type: "clientToolResult",
|
|
182
|
+
callId,
|
|
183
|
+
result: await tool.execute(args)
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
postMessage({
|
|
187
|
+
type: "clientToolResult",
|
|
188
|
+
callId,
|
|
189
|
+
error: serializeError(error)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
})();
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
96
195
|
}
|
|
97
196
|
};
|
|
98
197
|
const handleIframeLoad = () => {
|
|
@@ -112,6 +211,8 @@ function useChatbotFrameHandler({ setOpen, setIsExpanded, setIsFullScreen }) {
|
|
|
112
211
|
setAppName,
|
|
113
212
|
setSessionToken,
|
|
114
213
|
sendAppMessage,
|
|
214
|
+
registerClientTool,
|
|
215
|
+
unregisterClientTool,
|
|
115
216
|
setIframeEl,
|
|
116
217
|
isReady,
|
|
117
218
|
isInitialized
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chatbot-frame-handler.mjs","names":[],"sources":["../../../src/components/provider/use-chatbot-frame-handler.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\n\nexport type IframeMessage =\n | { type: \"CHATBOT_READY\" }\n | { type: \"CHATBOT_INITIALIZED\" }\n | { type: \"CLOSE_CHATBOT\" }\n | { type: \"EXPAND_CHATBOT\" }\n | { type: \"SHRINK_CHATBOT\" }\n | { type: \"FULLSCREEN_CHATBOT\" }\n | { type: \"EXIT_FULLSCREEN_CHATBOT\" }\n | { type: \"RELOAD_CHATBOT\" };\n\nexport type MCP = {\n name: string;\n url: string;\n headers?: Record<string, string>;\n};\n\nexport type InitProps = {\n mcps?: MCP[];\n appName?: string;\n sessionToken?: string;\n};\n\nexport type AppMessageAttachment = {\n fileName: string;\n mediaType: string;\n url: string;\n};\n\nexport type SendAppMessagePayload = {\n title?: string;\n message: string;\n hiddenMessage?: string;\n startOnNewChat?: boolean;\n attachments?: AppMessageAttachment[];\n};\n\nexport type ParentMessage =\n | {\n type: \"mcps\";\n mcps: MCP[];\n }\n | {\n type: \"appName\";\n appName: string;\n }\n | {\n type: \"sessionToken\";\n sessionToken: string;\n }\n | {\n type: \"init\";\n mcps?: MCP[];\n appName?: string;\n sessionToken?: string;\n }\n | {\n type: \"appMessage\";\n payload: SendAppMessagePayload;\n };\n\nexport function useChatbotFrameHandler({\n setOpen,\n setIsExpanded,\n setIsFullScreen,\n}: {\n setOpen: (open: boolean) => void;\n setIsExpanded: (expanded: boolean) => void;\n setIsFullScreen: (fullScreen: boolean) => void;\n}) {\n const [iframeEl, setIframeEl] = React.useState<HTMLIFrameElement | null>(\n null,\n );\n const [isReady, setIsReady] = React.useState(false);\n const [isInitialized, setIsInitialized] = React.useState(false);\n const pendingAppMessageRef = React.useRef<SendAppMessagePayload | null>(null);\n\n // helper to post message to the iframe\n const postMessage = React.useCallback(\n (message: ParentMessage) => {\n if (iframeEl?.contentWindow) {\n if (process.env.NODE_ENV === \"development\") {\n console.log(\"sending message to iframe\", message);\n }\n iframeEl.contentWindow.postMessage(message, \"*\");\n return true;\n }\n return false;\n },\n [iframeEl],\n );\n\n const init = React.useCallback(\n (props: InitProps) => {\n postMessage({\n type: \"init\",\n ...props,\n });\n },\n [postMessage],\n );\n\n const setMcps = React.useCallback(\n (mcps: MCP[]) => {\n postMessage({ type: \"mcps\", mcps });\n },\n [postMessage],\n );\n\n const setAppName = React.useCallback(\n (appName: string) => {\n postMessage({ type: \"appName\", appName });\n },\n [postMessage],\n );\n\n const setSessionToken = React.useCallback(\n (sessionToken: string) => {\n postMessage({ type: \"sessionToken\", sessionToken });\n },\n [postMessage],\n );\n\n const flushPendingAppMessage = React.useCallback(() => {\n const pendingPayload = pendingAppMessageRef.current;\n if (!pendingPayload || !isReady || !isInitialized) {\n return false;\n }\n\n const sent = postMessage({\n type: \"appMessage\",\n payload: pendingPayload,\n });\n\n if (sent) {\n pendingAppMessageRef.current = null;\n }\n\n return sent;\n }, [isInitialized, isReady, postMessage]);\n\n const sendAppMessage = React.useCallback(\n (payload: SendAppMessagePayload) => {\n pendingAppMessageRef.current = {\n ...payload,\n startOnNewChat: payload.startOnNewChat ?? true,\n };\n setOpen(true);\n flushPendingAppMessage();\n },\n [flushPendingAppMessage, setOpen],\n );\n\n React.useEffect(() => {\n flushPendingAppMessage();\n }, [flushPendingAppMessage]);\n\n // Initialize and setup listeners\n React.useEffect(() => {\n const iframe = iframeEl;\n if (!iframe) {\n return;\n }\n\n const handleMessage = (event: MessageEvent<IframeMessage>) => {\n if (event.source !== iframe.contentWindow) {\n return;\n }\n if (process.env.NODE_ENV === \"development\") {\n console.log(\"iframe message\", event.data);\n }\n // Handle iframe messages\n switch (event.data?.type) {\n case \"CHATBOT_READY\":\n setIsReady(true);\n break;\n case \"CHATBOT_INITIALIZED\":\n setIsInitialized(true);\n break;\n case \"CLOSE_CHATBOT\":\n setOpen(false);\n break;\n case \"EXPAND_CHATBOT\":\n setIsExpanded(true);\n break;\n case \"SHRINK_CHATBOT\":\n setIsExpanded(false);\n break;\n case \"FULLSCREEN_CHATBOT\":\n setIsFullScreen(true);\n break;\n case \"EXIT_FULLSCREEN_CHATBOT\":\n setIsFullScreen(false);\n break;\n case \"RELOAD_CHATBOT\":\n // reload iframe\n iframe.src = iframe.src;\n break;\n }\n };\n\n const handleIframeLoad = () => {\n setIsReady(false);\n setIsInitialized(false);\n };\n\n window.addEventListener(\"message\", handleMessage);\n iframe.addEventListener(\"load\", handleIframeLoad);\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n iframe.removeEventListener(\"load\", handleIframeLoad);\n };\n }, [iframeEl]); // Run when iframe gets mounted\n\n return {\n init,\n setMcps,\n setAppName,\n setSessionToken,\n sendAppMessage,\n setIframeEl,\n isReady,\n isInitialized,\n };\n}\n"],"mappings":";;;;;;AAgEA,SAAgB,uBAAuB,EACrC,SACA,eACA,mBAKC;CACD,MAAM,CAAC,UAAU,eAAe,MAAM,SACpC,KACD;CACD,MAAM,CAAC,SAAS,cAAc,MAAM,SAAS,MAAM;CACnD,MAAM,CAAC,eAAe,oBAAoB,MAAM,SAAS,MAAM;CAC/D,MAAM,uBAAuB,MAAM,OAAqC,KAAK;CAG7E,MAAM,cAAc,MAAM,aACvB,YAA2B;AAC1B,0DAAI,SAAU,eAAe;AAC3B,OAAI,QAAQ,IAAI,aAAa,cAC3B,SAAQ,IAAI,6BAA6B,QAAQ;AAEnD,YAAS,cAAc,YAAY,SAAS,IAAI;AAChD,UAAO;;AAET,SAAO;IAET,CAAC,SAAS,CACX;CAED,MAAM,OAAO,MAAM,aAChB,UAAqB;AACpB,+BACE,MAAM,UACH,OACH;IAEJ,CAAC,YAAY,CACd;CAED,MAAM,UAAU,MAAM,aACnB,SAAgB;AACf,cAAY;GAAE,MAAM;GAAQ;GAAM,CAAC;IAErC,CAAC,YAAY,CACd;CAED,MAAM,aAAa,MAAM,aACtB,YAAoB;AACnB,cAAY;GAAE,MAAM;GAAW;GAAS,CAAC;IAE3C,CAAC,YAAY,CACd;CAED,MAAM,kBAAkB,MAAM,aAC3B,iBAAyB;AACxB,cAAY;GAAE,MAAM;GAAgB;GAAc,CAAC;IAErD,CAAC,YAAY,CACd;CAED,MAAM,yBAAyB,MAAM,kBAAkB;EACrD,MAAM,iBAAiB,qBAAqB;AAC5C,MAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,cAClC,QAAO;EAGT,MAAM,OAAO,YAAY;GACvB,MAAM;GACN,SAAS;GACV,CAAC;AAEF,MAAI,KACF,sBAAqB,UAAU;AAGjC,SAAO;IACN;EAAC;EAAe;EAAS;EAAY,CAAC;CAEzC,MAAM,iBAAiB,MAAM,aAC1B,YAAmC;;AAClC,uBAAqB,4CAChB,gBACH,yCAAgB,QAAQ,uFAAkB;AAE5C,UAAQ,KAAK;AACb,0BAAwB;IAE1B,CAAC,wBAAwB,QAAQ,CAClC;AAED,OAAM,gBAAgB;AACpB,0BAAwB;IACvB,CAAC,uBAAuB,CAAC;AAG5B,OAAM,gBAAgB;EACpB,MAAM,SAAS;AACf,MAAI,CAAC,OACH;EAGF,MAAM,iBAAiB,UAAuC;;AAC5D,OAAI,MAAM,WAAW,OAAO,cAC1B;AAEF,OAAI,QAAQ,IAAI,aAAa,cAC3B,SAAQ,IAAI,kBAAkB,MAAM,KAAK;AAG3C,0BAAQ,MAAM,gEAAM,MAApB;IACE,KAAK;AACH,gBAAW,KAAK;AAChB;IACF,KAAK;AACH,sBAAiB,KAAK;AACtB;IACF,KAAK;AACH,aAAQ,MAAM;AACd;IACF,KAAK;AACH,mBAAc,KAAK;AACnB;IACF,KAAK;AACH,mBAAc,MAAM;AACpB;IACF,KAAK;AACH,qBAAgB,KAAK;AACrB;IACF,KAAK;AACH,qBAAgB,MAAM;AACtB;IACF,KAAK;AAEH,YAAO,MAAM,OAAO;AACpB;;;EAIN,MAAM,yBAAyB;AAC7B,cAAW,MAAM;AACjB,oBAAiB,MAAM;;AAGzB,SAAO,iBAAiB,WAAW,cAAc;AACjD,SAAO,iBAAiB,QAAQ,iBAAiB;AAEjD,eAAa;AACX,UAAO,oBAAoB,WAAW,cAAc;AACpD,UAAO,oBAAoB,QAAQ,iBAAiB;;IAErD,CAAC,SAAS,CAAC;AAEd,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|
|
1
|
+
{"version":3,"file":"use-chatbot-frame-handler.mjs","names":[],"sources":["../../../src/components/provider/use-chatbot-frame-handler.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport type {\n ClientToolDefinition,\n ClientToolExecuteRequest,\n RegisteredClientTool,\n} from \"../../lib/client-tools\";\n\nexport type IframeMessage =\n | { type: \"CHATBOT_READY\" }\n | { type: \"CHATBOT_INITIALIZED\" }\n | { type: \"CLOSE_CHATBOT\" }\n | { type: \"EXPAND_CHATBOT\" }\n | { type: \"SHRINK_CHATBOT\" }\n | { type: \"FULLSCREEN_CHATBOT\" }\n | { type: \"EXIT_FULLSCREEN_CHATBOT\" }\n | { type: \"RELOAD_CHATBOT\" }\n | ({\n type: \"EXECUTE_CLIENT_TOOL\";\n } & ClientToolExecuteRequest);\n\nexport type MCP = {\n name: string;\n url: string;\n headers?: Record<string, string>;\n};\n\nexport type InitProps = {\n mcps?: MCP[];\n appName?: string;\n sessionToken?: string;\n};\n\nexport type AppMessageAttachment = {\n /** 添付ファイルの表示名です。 */\n fileName: string;\n /** 添付ファイルの MIME type です。 */\n mediaType: string;\n /** 添付ファイルを取得できる URL です。 */\n url: string;\n};\n\nexport type SendAppMessagePayload = {\n /** チャット上に表示するタイトルです。 */\n title?: string;\n /** ユーザーに表示するメッセージ本文です。 */\n message: string;\n /** UI に表示せず、裏側のコンテキストとして渡すメッセージです。 */\n hiddenMessage?: string;\n /** 新規チャットを開始してから送るかを指定します。 */\n startOnNewChat?: boolean;\n /** メッセージと一緒に渡す添付ファイル一覧です。 */\n attachments?: AppMessageAttachment[];\n};\n\nexport type ParentMessage =\n | {\n type: \"mcps\";\n mcps: MCP[];\n }\n | {\n type: \"appName\";\n appName: string;\n }\n | {\n type: \"sessionToken\";\n sessionToken: string;\n }\n | {\n type: \"init\";\n mcps?: MCP[];\n appName?: string;\n sessionToken?: string;\n }\n | {\n type: \"appMessage\";\n payload: SendAppMessagePayload;\n }\n | {\n type: \"registerClientTools\";\n tools: ClientToolDefinition[];\n }\n | {\n type: \"unregisterClientTools\";\n names: string[];\n }\n | {\n type: \"clientToolResult\";\n callId: string;\n result?: unknown;\n error?: string;\n };\n\nfunction serializeError(error: unknown) {\n if (error instanceof Error) {\n return error.message;\n }\n\n if (typeof error === \"string\") {\n return error;\n }\n\n return \"Unknown client tool error\";\n}\n\ntype UseChatbotFrameHandlerProps = {\n /** チャットボットの開閉状態を更新します。 */\n setOpen: (open: boolean) => void;\n /** 展開状態を更新します。 */\n setIsExpanded: (expanded: boolean) => void;\n /** フルスクリーン状態を更新します。 */\n setIsFullScreen: (fullScreen: boolean) => void;\n};\n\n/**\n * iframe と親アプリ間のメッセージ送受信を管理し、チャットボット制御 API をまとめます。\n *\n * @returns iframe 初期化、メッセージ送信、Client Tool 登録に使うハンドラ一式を返します。\n *\n * @example\n * ```tsx\n * export function FrameBridge() {\n * const [open, setOpen] = React.useState(false);\n * const [isExpanded, setIsExpanded] = React.useState(false);\n * const [isFullScreen, setIsFullScreen] = React.useState(false);\n * const { setIframeEl, init, sendAppMessage, isReady } =\n * useChatbotFrameHandler({\n * setOpen,\n * setIsExpanded,\n * setIsFullScreen,\n * });\n *\n * React.useEffect(() => {\n * if (isReady) {\n * init({ appName: \"EC Force\" });\n * sendAppMessage({ message: \"在庫を確認したいです\" });\n * }\n * }, [init, isReady, sendAppMessage]);\n *\n * return <iframe ref={setIframeEl} src=\"https://example.com/embed\" />;\n * }\n * ```\n */\nexport function useChatbotFrameHandler({\n setOpen,\n setIsExpanded,\n setIsFullScreen,\n}: UseChatbotFrameHandlerProps) {\n const [iframeEl, setIframeEl] = React.useState<HTMLIFrameElement | null>(\n null,\n );\n const [isReady, setIsReady] = React.useState(false);\n const [isInitialized, setIsInitialized] = React.useState(false);\n const pendingAppMessageRef = React.useRef<SendAppMessagePayload | null>(null);\n const clientToolRegistryRef = React.useRef(\n new Map<string, RegisteredClientTool>(),\n );\n\n // helper to post message to the iframe\n const postMessage = React.useCallback(\n (message: ParentMessage) => {\n if (iframeEl?.contentWindow) {\n if (process.env.NODE_ENV === \"development\") {\n console.log(\"sending message to iframe\", message);\n }\n iframeEl.contentWindow.postMessage(message, \"*\");\n return true;\n }\n return false;\n },\n [iframeEl],\n );\n\n const init = React.useCallback(\n (props: InitProps) => {\n postMessage({\n type: \"init\",\n ...props,\n });\n },\n [postMessage],\n );\n\n const setMcps = React.useCallback(\n (mcps: MCP[]) => {\n postMessage({ type: \"mcps\", mcps });\n },\n [postMessage],\n );\n\n const setAppName = React.useCallback(\n (appName: string) => {\n postMessage({ type: \"appName\", appName });\n },\n [postMessage],\n );\n\n const setSessionToken = React.useCallback(\n (sessionToken: string) => {\n postMessage({ type: \"sessionToken\", sessionToken });\n },\n [postMessage],\n );\n\n const flushPendingAppMessage = React.useCallback(() => {\n const pendingPayload = pendingAppMessageRef.current;\n if (!pendingPayload || !isReady || !isInitialized) {\n return false;\n }\n\n const sent = postMessage({\n type: \"appMessage\",\n payload: pendingPayload,\n });\n\n if (sent) {\n pendingAppMessageRef.current = null;\n }\n\n return sent;\n }, [isInitialized, isReady, postMessage]);\n\n const sendAppMessage = React.useCallback(\n (payload: SendAppMessagePayload) => {\n pendingAppMessageRef.current = {\n ...payload,\n startOnNewChat: payload.startOnNewChat ?? true,\n };\n setOpen(true);\n flushPendingAppMessage();\n },\n [flushPendingAppMessage, setOpen],\n );\n\n React.useEffect(() => {\n flushPendingAppMessage();\n }, [flushPendingAppMessage]);\n\n const registerClientTool = React.useCallback(\n (tool: RegisteredClientTool) => {\n clientToolRegistryRef.current.set(tool.definition.name, tool);\n if (!isReady || !isInitialized) {\n return;\n }\n\n postMessage({\n type: \"registerClientTools\",\n tools: [tool.definition],\n });\n },\n [isInitialized, isReady, postMessage],\n );\n\n const unregisterClientTool = React.useCallback(\n (name: string) => {\n const deleted = clientToolRegistryRef.current.delete(name);\n if (!deleted || !isReady || !isInitialized) {\n return;\n }\n\n postMessage({\n type: \"unregisterClientTools\",\n names: [name],\n });\n },\n [isInitialized, isReady, postMessage],\n );\n\n React.useEffect(() => {\n if (!isReady || !isInitialized) {\n return;\n }\n\n const tools = [...clientToolRegistryRef.current.values()].map(\n (tool) => tool.definition,\n );\n if (tools.length === 0) {\n return;\n }\n\n postMessage({\n type: \"registerClientTools\",\n tools,\n });\n }, [isInitialized, isReady, postMessage]);\n\n // Initialize and setup listeners\n React.useEffect(() => {\n const iframe = iframeEl;\n if (!iframe) {\n return;\n }\n\n const handleMessage = (event: MessageEvent<IframeMessage>) => {\n if (event.source !== iframe.contentWindow) {\n return;\n }\n if (process.env.NODE_ENV === \"development\") {\n console.log(\"iframe message\", event.data);\n }\n // Handle iframe messages\n switch (event.data?.type) {\n case \"CHATBOT_READY\":\n setIsReady(true);\n break;\n case \"CHATBOT_INITIALIZED\":\n setIsInitialized(true);\n break;\n case \"CLOSE_CHATBOT\":\n setOpen(false);\n break;\n case \"EXPAND_CHATBOT\":\n setIsExpanded(true);\n break;\n case \"SHRINK_CHATBOT\":\n setIsExpanded(false);\n break;\n case \"FULLSCREEN_CHATBOT\":\n setIsFullScreen(true);\n break;\n case \"EXIT_FULLSCREEN_CHATBOT\":\n setIsFullScreen(false);\n break;\n case \"RELOAD_CHATBOT\":\n // reload iframe\n iframe.src = iframe.src;\n break;\n case \"EXECUTE_CLIENT_TOOL\": {\n const { callId, name, args } = event.data;\n const tool = clientToolRegistryRef.current.get(name);\n if (!tool) {\n postMessage({\n type: \"clientToolResult\",\n callId,\n error: `Client tool not found: ${name}`,\n });\n break;\n }\n\n void (async () => {\n try {\n const result = await tool.execute(args);\n postMessage({\n type: \"clientToolResult\",\n callId,\n result,\n });\n } catch (error) {\n postMessage({\n type: \"clientToolResult\",\n callId,\n error: serializeError(error),\n });\n }\n })();\n break;\n }\n }\n };\n\n const handleIframeLoad = () => {\n setIsReady(false);\n setIsInitialized(false);\n };\n\n window.addEventListener(\"message\", handleMessage);\n iframe.addEventListener(\"load\", handleIframeLoad);\n\n return () => {\n window.removeEventListener(\"message\", handleMessage);\n iframe.removeEventListener(\"load\", handleIframeLoad);\n };\n }, [iframeEl]); // Run when iframe gets mounted\n\n return {\n init,\n setMcps,\n setAppName,\n setSessionToken,\n sendAppMessage,\n registerClientTool,\n unregisterClientTool,\n setIframeEl,\n isReady,\n isInitialized,\n };\n}\n"],"mappings":";;;;;;AA8FA,SAAS,eAAe,OAAgB;AACtC,KAAI,iBAAiB,MACnB,QAAO,MAAM;AAGf,KAAI,OAAO,UAAU,SACnB,QAAO;AAGT,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCT,SAAgB,uBAAuB,EACrC,SACA,eACA,mBAC8B;CAC9B,MAAM,CAAC,UAAU,eAAe,MAAM,SACpC,KACD;CACD,MAAM,CAAC,SAAS,cAAc,MAAM,SAAS,MAAM;CACnD,MAAM,CAAC,eAAe,oBAAoB,MAAM,SAAS,MAAM;CAC/D,MAAM,uBAAuB,MAAM,OAAqC,KAAK;CAC7E,MAAM,wBAAwB,MAAM,uBAClC,IAAI,KAAmC,CACxC;CAGD,MAAM,cAAc,MAAM,aACvB,YAA2B;AAC1B,0DAAI,SAAU,eAAe;AAC3B,OAAI,QAAQ,IAAI,aAAa,cAC3B,SAAQ,IAAI,6BAA6B,QAAQ;AAEnD,YAAS,cAAc,YAAY,SAAS,IAAI;AAChD,UAAO;;AAET,SAAO;IAET,CAAC,SAAS,CACX;CAED,MAAM,OAAO,MAAM,aAChB,UAAqB;AACpB,+BACE,MAAM,UACH,OACH;IAEJ,CAAC,YAAY,CACd;CAED,MAAM,UAAU,MAAM,aACnB,SAAgB;AACf,cAAY;GAAE,MAAM;GAAQ;GAAM,CAAC;IAErC,CAAC,YAAY,CACd;CAED,MAAM,aAAa,MAAM,aACtB,YAAoB;AACnB,cAAY;GAAE,MAAM;GAAW;GAAS,CAAC;IAE3C,CAAC,YAAY,CACd;CAED,MAAM,kBAAkB,MAAM,aAC3B,iBAAyB;AACxB,cAAY;GAAE,MAAM;GAAgB;GAAc,CAAC;IAErD,CAAC,YAAY,CACd;CAED,MAAM,yBAAyB,MAAM,kBAAkB;EACrD,MAAM,iBAAiB,qBAAqB;AAC5C,MAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,cAClC,QAAO;EAGT,MAAM,OAAO,YAAY;GACvB,MAAM;GACN,SAAS;GACV,CAAC;AAEF,MAAI,KACF,sBAAqB,UAAU;AAGjC,SAAO;IACN;EAAC;EAAe;EAAS;EAAY,CAAC;CAEzC,MAAM,iBAAiB,MAAM,aAC1B,YAAmC;;AAClC,uBAAqB,4CAChB,gBACH,yCAAgB,QAAQ,uFAAkB;AAE5C,UAAQ,KAAK;AACb,0BAAwB;IAE1B,CAAC,wBAAwB,QAAQ,CAClC;AAED,OAAM,gBAAgB;AACpB,0BAAwB;IACvB,CAAC,uBAAuB,CAAC;CAE5B,MAAM,qBAAqB,MAAM,aAC9B,SAA+B;AAC9B,wBAAsB,QAAQ,IAAI,KAAK,WAAW,MAAM,KAAK;AAC7D,MAAI,CAAC,WAAW,CAAC,cACf;AAGF,cAAY;GACV,MAAM;GACN,OAAO,CAAC,KAAK,WAAW;GACzB,CAAC;IAEJ;EAAC;EAAe;EAAS;EAAY,CACtC;CAED,MAAM,uBAAuB,MAAM,aAChC,SAAiB;AAEhB,MAAI,CADY,sBAAsB,QAAQ,OAAO,KAAK,IAC1C,CAAC,WAAW,CAAC,cAC3B;AAGF,cAAY;GACV,MAAM;GACN,OAAO,CAAC,KAAK;GACd,CAAC;IAEJ;EAAC;EAAe;EAAS;EAAY,CACtC;AAED,OAAM,gBAAgB;AACpB,MAAI,CAAC,WAAW,CAAC,cACf;EAGF,MAAM,QAAQ,CAAC,GAAG,sBAAsB,QAAQ,QAAQ,CAAC,CAAC,KACvD,SAAS,KAAK,WAChB;AACD,MAAI,MAAM,WAAW,EACnB;AAGF,cAAY;GACV,MAAM;GACN;GACD,CAAC;IACD;EAAC;EAAe;EAAS;EAAY,CAAC;AAGzC,OAAM,gBAAgB;EACpB,MAAM,SAAS;AACf,MAAI,CAAC,OACH;EAGF,MAAM,iBAAiB,UAAuC;;AAC5D,OAAI,MAAM,WAAW,OAAO,cAC1B;AAEF,OAAI,QAAQ,IAAI,aAAa,cAC3B,SAAQ,IAAI,kBAAkB,MAAM,KAAK;AAG3C,0BAAQ,MAAM,gEAAM,MAApB;IACE,KAAK;AACH,gBAAW,KAAK;AAChB;IACF,KAAK;AACH,sBAAiB,KAAK;AACtB;IACF,KAAK;AACH,aAAQ,MAAM;AACd;IACF,KAAK;AACH,mBAAc,KAAK;AACnB;IACF,KAAK;AACH,mBAAc,MAAM;AACpB;IACF,KAAK;AACH,qBAAgB,KAAK;AACrB;IACF,KAAK;AACH,qBAAgB,MAAM;AACtB;IACF,KAAK;AAEH,YAAO,MAAM,OAAO;AACpB;IACF,KAAK,uBAAuB;KAC1B,MAAM,EAAE,QAAQ,MAAM,SAAS,MAAM;KACrC,MAAM,OAAO,sBAAsB,QAAQ,IAAI,KAAK;AACpD,SAAI,CAAC,MAAM;AACT,kBAAY;OACV,MAAM;OACN;OACA,OAAO,0BAA0B;OAClC,CAAC;AACF;;AAGF,MAAM,YAAY;AAChB,UAAI;AAEF,mBAAY;QACV,MAAM;QACN;QACA,QAJa,MAAM,KAAK,QAAQ,KAAK;QAKtC,CAAC;eACK,OAAO;AACd,mBAAY;QACV,MAAM;QACN;QACA,OAAO,eAAe,MAAM;QAC7B,CAAC;;SAEF;AACJ;;;;EAKN,MAAM,yBAAyB;AAC7B,cAAW,MAAM;AACjB,oBAAiB,MAAM;;AAGzB,SAAO,iBAAiB,WAAW,cAAc;AACjD,SAAO,iBAAiB,QAAQ,iBAAiB;AAEjD,eAAa;AACX,UAAO,oBAAoB,WAAW,cAAc;AACpD,UAAO,oBAAoB,QAAQ,iBAAiB;;IAErD,CAAC,SAAS,CAAC;AAEd,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|
|
@@ -3,6 +3,31 @@
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/components/provider/use-chatbot-window-states.tsx
|
|
6
|
+
/**
|
|
7
|
+
* チャットボット UI の開閉・展開・フルスクリーン状態を管理します。
|
|
8
|
+
*
|
|
9
|
+
* @returns 初回オープン判定と、各表示状態、その更新関数を返します。
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* export function ChatbotWindowController() {
|
|
14
|
+
* const { open, setOpen, isExpanded, setIsExpanded } =
|
|
15
|
+
* useChatbotWindowStates();
|
|
16
|
+
*
|
|
17
|
+
* return (
|
|
18
|
+
* <button
|
|
19
|
+
* type="button"
|
|
20
|
+
* onClick={() => {
|
|
21
|
+
* setOpen(!open);
|
|
22
|
+
* setIsExpanded(!isExpanded);
|
|
23
|
+
* }}
|
|
24
|
+
* >
|
|
25
|
+
* Toggle window
|
|
26
|
+
* </button>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
6
31
|
function useChatbotWindowStates() {
|
|
7
32
|
const [hasOpened, setHasOpened] = React.useState(false);
|
|
8
33
|
const [open, setOpen] = React.useState(false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chatbot-window-states.mjs","names":[],"sources":["../../../src/components/provider/use-chatbot-window-states.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\n\nexport function useChatbotWindowStates() {\n const [hasOpened, setHasOpened] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const [isExpanded, setIsExpanded] = React.useState(false);\n const [isFullScreen, setIsFullScreen] = React.useState(false);\n\n React.useEffect(() => {\n if (open) {\n setHasOpened(true);\n }\n }, [open]);\n\n return {\n hasOpened,\n open,\n isExpanded,\n isFullScreen,\n setOpen,\n setIsExpanded,\n setIsFullScreen,\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-chatbot-window-states.mjs","names":[],"sources":["../../../src/components/provider/use-chatbot-window-states.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\n\n/**\n * チャットボット UI の開閉・展開・フルスクリーン状態を管理します。\n *\n * @returns 初回オープン判定と、各表示状態、その更新関数を返します。\n *\n * @example\n * ```tsx\n * export function ChatbotWindowController() {\n * const { open, setOpen, isExpanded, setIsExpanded } =\n * useChatbotWindowStates();\n *\n * return (\n * <button\n * type=\"button\"\n * onClick={() => {\n * setOpen(!open);\n * setIsExpanded(!isExpanded);\n * }}\n * >\n * Toggle window\n * </button>\n * );\n * }\n * ```\n */\nexport function useChatbotWindowStates() {\n const [hasOpened, setHasOpened] = React.useState(false);\n const [open, setOpen] = React.useState(false);\n const [isExpanded, setIsExpanded] = React.useState(false);\n const [isFullScreen, setIsFullScreen] = React.useState(false);\n\n React.useEffect(() => {\n if (open) {\n setHasOpened(true);\n }\n }, [open]);\n\n return {\n hasOpened,\n open,\n isExpanded,\n isFullScreen,\n setOpen,\n setIsExpanded,\n setIsFullScreen,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,yBAAyB;CACvC,MAAM,CAAC,WAAW,gBAAgB,MAAM,SAAS,MAAM;CACvD,MAAM,CAAC,MAAM,WAAW,MAAM,SAAS,MAAM;CAC7C,MAAM,CAAC,YAAY,iBAAiB,MAAM,SAAS,MAAM;CACzD,MAAM,CAAC,cAAc,mBAAmB,MAAM,SAAS,MAAM;AAE7D,OAAM,gBAAgB;AACpB,MAAI,KACF,cAAa,KAAK;IAEnB,CAAC,KAAK,CAAC;AAEV,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as React$1 from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
3
3
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
4
4
|
|
|
5
5
|
//#region src/components/sheet.d.ts
|
|
6
6
|
declare function Sheet({
|
|
7
7
|
...props
|
|
8
|
-
}: React$1.ComponentProps<typeof SheetPrimitive.Root>):
|
|
8
|
+
}: React$1.ComponentProps<typeof SheetPrimitive.Root>): react_jsx_runtime2.JSX.Element;
|
|
9
9
|
declare const SheetTrigger: React$1.ForwardRefExoticComponent<Omit<SheetPrimitive.DialogTriggerProps & React$1.RefAttributes<HTMLButtonElement>, "ref"> & React$1.RefAttributes<HTMLButtonElement>>;
|
|
10
10
|
declare const SheetClose: React$1.ForwardRefExoticComponent<Omit<SheetPrimitive.DialogCloseProps & React$1.RefAttributes<HTMLButtonElement>, "ref"> & React$1.RefAttributes<HTMLButtonElement>>;
|
|
11
11
|
declare const SheetContent: React$1.ForwardRefExoticComponent<Omit<SheetPrimitive.DialogContentProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime3 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region src/components/tooltip.d.ts
|
|
4
4
|
type Side = "top" | "right" | "bottom" | "left";
|
|
@@ -32,7 +32,7 @@ declare function Tooltip({
|
|
|
32
32
|
sideOffset,
|
|
33
33
|
arrowClassName,
|
|
34
34
|
keepTooltipOpen
|
|
35
|
-
}: TooltipProps):
|
|
35
|
+
}: TooltipProps): react_jsx_runtime3.JSX.Element;
|
|
36
36
|
//#endregion
|
|
37
37
|
export { Tooltip };
|
|
38
38
|
//# sourceMappingURL=tooltip.d.mts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { ClientToolDefinition, ClientToolExecuteRequest, ClientToolJsonSchema, RegisteredClientTool, UseClientToolOptions, useClientTool } from "./lib/client-tools.mjs";
|
|
1
2
|
import { ChatbotProvider, useChatbot } from "./components/provider/chatbot-provider.mjs";
|
|
2
3
|
import { ChatbotFrame, ChatbotFrameProps, SessionData, UseSessionProps, useSession } from "./components/chatbot-frame.mjs";
|
|
3
4
|
import { ChatbotSheet } from "./components/chatbot-sheet.mjs";
|
|
4
5
|
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "./components/sheet.mjs";
|
|
5
6
|
import { Tooltip } from "./components/tooltip.mjs";
|
|
6
|
-
export { ChatbotFrame, ChatbotFrameProps, ChatbotProvider, ChatbotSheet, SessionData, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Tooltip, UseSessionProps, useChatbot, useSession };
|
|
7
|
+
export { ChatbotFrame, ChatbotFrameProps, ChatbotProvider, ChatbotSheet, ClientToolDefinition, ClientToolExecuteRequest, ClientToolJsonSchema, RegisteredClientTool, SessionData, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Tooltip, UseClientToolOptions, UseSessionProps, useChatbot, useClientTool, useSession };
|
package/dist/index.mjs
CHANGED
|
@@ -3,5 +3,6 @@ import { ChatbotFrame, useSession } from "./components/chatbot-frame.mjs";
|
|
|
3
3
|
import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "./components/sheet.mjs";
|
|
4
4
|
import { Tooltip } from "./components/tooltip.mjs";
|
|
5
5
|
import { ChatbotSheet } from "./components/chatbot-sheet.mjs";
|
|
6
|
+
import { useClientTool } from "./lib/client-tools.mjs";
|
|
6
7
|
|
|
7
|
-
export { ChatbotFrame, ChatbotProvider, ChatbotSheet, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Tooltip, useChatbot, useSession };
|
|
8
|
+
export { ChatbotFrame, ChatbotProvider, ChatbotSheet, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, Tooltip, useChatbot, useClientTool, useSession };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/client-tools.d.ts
|
|
4
|
+
type ClientToolJsonSchema = Record<string, unknown>;
|
|
5
|
+
type ClientToolDefinition = {
|
|
6
|
+
name: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
description: string;
|
|
9
|
+
summary?: string;
|
|
10
|
+
requireConfirmation?: boolean;
|
|
11
|
+
parameters: ClientToolJsonSchema;
|
|
12
|
+
};
|
|
13
|
+
type ClientToolExecuteRequest = {
|
|
14
|
+
callId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
args: unknown;
|
|
17
|
+
};
|
|
18
|
+
type RegisteredClientTool = {
|
|
19
|
+
definition: ClientToolDefinition;
|
|
20
|
+
execute: (args: unknown) => Promise<unknown> | unknown;
|
|
21
|
+
};
|
|
22
|
+
type UseClientToolOptions<TParameters extends z.ZodTypeAny> = {
|
|
23
|
+
/** ツール名です。`page_`のプレフィックスが自動で追加されます。 */
|
|
24
|
+
name: string;
|
|
25
|
+
/** UI上に表示するツール名です。 */
|
|
26
|
+
displayName: string;
|
|
27
|
+
/** LLMが見れるツールの詳細説明です。 */
|
|
28
|
+
description: string;
|
|
29
|
+
/** ユーザーに表示するツールの要約です。 */
|
|
30
|
+
summary?: string;
|
|
31
|
+
/** 実行前にユーザー確認を要求するかを指定します。 */
|
|
32
|
+
requireConfirmation?: boolean;
|
|
33
|
+
/** ツールの引数を定義するZodスキーマです。 */
|
|
34
|
+
parameters: TParameters;
|
|
35
|
+
/** 検証済み引数を受け取り、ツール本体の処理を実行します。 */
|
|
36
|
+
execute: (args: z.output<TParameters>) => Promise<unknown> | unknown;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* チャットボットから呼び出せるクライアントツールを登録します。
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* import { z } from "zod";
|
|
44
|
+
* import { useClientTool } from "@super_studio/ecforce-ai-agent-react";
|
|
45
|
+
*
|
|
46
|
+
* export function InventoryToolRegistration() {
|
|
47
|
+
* useClientTool({
|
|
48
|
+
* name: "lookup_inventory",
|
|
49
|
+
* displayName: "在庫確認",
|
|
50
|
+
* description: "商品コードから在庫数を取得します。",
|
|
51
|
+
* summary: "商品在庫を返します。",
|
|
52
|
+
* parameters: z.object({
|
|
53
|
+
* sku: z.string().describe("商品コード"),
|
|
54
|
+
* }),
|
|
55
|
+
* execute: async ({ sku }) => {
|
|
56
|
+
* return { sku, available: true };
|
|
57
|
+
* },
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* return null;
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare function useClientTool<TParameters extends z.ZodTypeAny>({
|
|
65
|
+
name,
|
|
66
|
+
displayName,
|
|
67
|
+
description,
|
|
68
|
+
summary,
|
|
69
|
+
requireConfirmation,
|
|
70
|
+
parameters,
|
|
71
|
+
execute
|
|
72
|
+
}: UseClientToolOptions<TParameters>): void;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { ClientToolDefinition, ClientToolExecuteRequest, ClientToolJsonSchema, RegisteredClientTool, UseClientToolOptions, useClientTool };
|
|
75
|
+
//# sourceMappingURL=client-tools.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-tools.d.mts","names":[],"sources":["../../src/lib/client-tools.ts"],"sourcesContent":[],"mappings":";;;KAMY,oBAAA,GAAuB;KAGvB,oBAAA;EAHA,IAAA,EAAA,MAAA;EAGA,WAAA,EAAA,MAAA;EASA,WAAA,EAAA,MAAA;EAMA,OAAA,CAAA,EAAA,MAAA;EAKA,mBAAA,CAAA,EAAA,OAAoB;EAAuB,UAAA,EAdzC,oBAcyC;CAYzC;AAEa,KAzBf,wBAAA,GAyBe;EAAP,MAAA,EAAA,MAAA;EAAwB,IAAA,EAAA,MAAA;EAAO,IAAA,EAAA,OAAA;AAqCnD,CAAA;AAAoD,KAxDxC,oBAAA,GAwDwC;EAClD,UAAA,EAxDY,oBAwDZ;EACA,OAAA,EAAA,CAAA,IAAA,EAAA,OAAA,EAAA,GAxD4B,OAwD5B,CAAA,OAAA,CAAA,GAAA,OAAA;CACA;AACA,KAvDU,oBAuDV,CAAA,oBAvDmD,CAAA,CAAE,UAuDrD,CAAA,GAAA;EACA;EACA,IAAA,EAAA,MAAA;EACA;EACsB,WAAA,EAAA,MAAA;EAArB;EAAoB,WAAA,EAAA,MAAA;;;;;;cA/CT;;kBAEI,CAAA,CAAE,OAAO,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqC5B,kCAAkC,CAAA,CAAE;;;;;;;;GAQjD,qBAAqB"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useChatbot } from "../components/provider/chatbot-provider.mjs";
|
|
4
|
+
import React from "react";
|
|
5
|
+
|
|
6
|
+
//#region src/lib/client-tools.ts
|
|
7
|
+
const CLIENT_TOOL_NAME_PREFIX = "page_";
|
|
8
|
+
function normalizeClientToolName(name) {
|
|
9
|
+
if (name.startsWith(CLIENT_TOOL_NAME_PREFIX)) return name;
|
|
10
|
+
return `${CLIENT_TOOL_NAME_PREFIX}${name}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* チャットボットから呼び出せるクライアントツールを登録します。
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* import { z } from "zod";
|
|
18
|
+
* import { useClientTool } from "@super_studio/ecforce-ai-agent-react";
|
|
19
|
+
*
|
|
20
|
+
* export function InventoryToolRegistration() {
|
|
21
|
+
* useClientTool({
|
|
22
|
+
* name: "lookup_inventory",
|
|
23
|
+
* displayName: "在庫確認",
|
|
24
|
+
* description: "商品コードから在庫数を取得します。",
|
|
25
|
+
* summary: "商品在庫を返します。",
|
|
26
|
+
* parameters: z.object({
|
|
27
|
+
* sku: z.string().describe("商品コード"),
|
|
28
|
+
* }),
|
|
29
|
+
* execute: async ({ sku }) => {
|
|
30
|
+
* return { sku, available: true };
|
|
31
|
+
* },
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* return null;
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
function useClientTool({ name, displayName, description, summary, requireConfirmation = false, parameters, execute }) {
|
|
39
|
+
const { registerClientTool, unregisterClientTool } = useChatbot();
|
|
40
|
+
const executeRef = React.useRef(execute);
|
|
41
|
+
const normalizedName = React.useMemo(() => normalizeClientToolName(name), [name]);
|
|
42
|
+
React.useEffect(() => {
|
|
43
|
+
executeRef.current = execute;
|
|
44
|
+
}, [execute]);
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
let isCancelled = false;
|
|
47
|
+
(async () => {
|
|
48
|
+
const { z } = await import("zod");
|
|
49
|
+
if (isCancelled) return;
|
|
50
|
+
registerClientTool({
|
|
51
|
+
definition: {
|
|
52
|
+
name: normalizedName,
|
|
53
|
+
displayName,
|
|
54
|
+
description,
|
|
55
|
+
summary,
|
|
56
|
+
requireConfirmation,
|
|
57
|
+
parameters: z.toJSONSchema(parameters)
|
|
58
|
+
},
|
|
59
|
+
execute: (args) => executeRef.current(args)
|
|
60
|
+
});
|
|
61
|
+
})();
|
|
62
|
+
return () => {
|
|
63
|
+
isCancelled = true;
|
|
64
|
+
unregisterClientTool(normalizedName);
|
|
65
|
+
};
|
|
66
|
+
}, [
|
|
67
|
+
description,
|
|
68
|
+
displayName,
|
|
69
|
+
normalizedName,
|
|
70
|
+
parameters,
|
|
71
|
+
registerClientTool,
|
|
72
|
+
requireConfirmation,
|
|
73
|
+
summary,
|
|
74
|
+
unregisterClientTool
|
|
75
|
+
]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { useClientTool };
|
|
80
|
+
//# sourceMappingURL=client-tools.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-tools.mjs","names":[],"sources":["../../src/lib/client-tools.ts"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport type { z } from \"zod\";\nimport { useChatbot } from \"../components/provider/chatbot-provider\";\n\nexport type ClientToolJsonSchema = Record<string, unknown>;\nconst CLIENT_TOOL_NAME_PREFIX = \"page_\";\n\nexport type ClientToolDefinition = {\n name: string;\n displayName: string;\n description: string;\n summary?: string;\n requireConfirmation?: boolean;\n parameters: ClientToolJsonSchema;\n};\n\nexport type ClientToolExecuteRequest = {\n callId: string;\n name: string;\n args: unknown;\n};\n\nexport type RegisteredClientTool = {\n definition: ClientToolDefinition;\n execute: (args: unknown) => Promise<unknown> | unknown;\n};\n\nexport type UseClientToolOptions<TParameters extends z.ZodTypeAny> = {\n /** ツール名です。`page_`のプレフィックスが自動で追加されます。 */\n name: string;\n /** UI上に表示するツール名です。 */\n displayName: string;\n /** LLMが見れるツールの詳細説明です。 */\n description: string;\n /** ユーザーに表示するツールの要約です。 */\n summary?: string;\n /** 実行前にユーザー確認を要求するかを指定します。 */\n requireConfirmation?: boolean;\n /** ツールの引数を定義するZodスキーマです。 */\n parameters: TParameters;\n /** 検証済み引数を受け取り、ツール本体の処理を実行します。 */\n execute: (args: z.output<TParameters>) => Promise<unknown> | unknown;\n};\n\nfunction normalizeClientToolName(name: string) {\n if (name.startsWith(CLIENT_TOOL_NAME_PREFIX)) {\n return name;\n }\n\n return `${CLIENT_TOOL_NAME_PREFIX}${name}`;\n}\n\n/**\n * チャットボットから呼び出せるクライアントツールを登録します。\n *\n * @example\n * ```tsx\n * import { z } from \"zod\";\n * import { useClientTool } from \"@super_studio/ecforce-ai-agent-react\";\n *\n * export function InventoryToolRegistration() {\n * useClientTool({\n * name: \"lookup_inventory\",\n * displayName: \"在庫確認\",\n * description: \"商品コードから在庫数を取得します。\",\n * summary: \"商品在庫を返します。\",\n * parameters: z.object({\n * sku: z.string().describe(\"商品コード\"),\n * }),\n * execute: async ({ sku }) => {\n * return { sku, available: true };\n * },\n * });\n *\n * return null;\n * }\n * ```\n */\nexport function useClientTool<TParameters extends z.ZodTypeAny>({\n name,\n displayName,\n description,\n summary,\n requireConfirmation = false,\n parameters,\n execute,\n}: UseClientToolOptions<TParameters>) {\n const { registerClientTool, unregisterClientTool } = useChatbot();\n const executeRef = React.useRef(execute);\n const normalizedName = React.useMemo(\n () => normalizeClientToolName(name),\n [name],\n );\n\n React.useEffect(() => {\n executeRef.current = execute;\n }, [execute]);\n\n React.useEffect(() => {\n let isCancelled = false;\n\n void (async () => {\n const { z } = await import(\"zod\");\n if (isCancelled) {\n return;\n }\n\n registerClientTool({\n definition: {\n name: normalizedName,\n displayName,\n description,\n summary,\n requireConfirmation,\n parameters: z.toJSONSchema(parameters),\n },\n execute: (args) => executeRef.current(args as z.output<TParameters>),\n });\n })();\n\n return () => {\n isCancelled = true;\n unregisterClientTool(normalizedName);\n };\n }, [\n description,\n displayName,\n normalizedName,\n parameters,\n registerClientTool,\n requireConfirmation,\n summary,\n unregisterClientTool,\n ]);\n}\n"],"mappings":";;;;;;AAOA,MAAM,0BAA0B;AAuChC,SAAS,wBAAwB,MAAc;AAC7C,KAAI,KAAK,WAAW,wBAAwB,CAC1C,QAAO;AAGT,QAAO,GAAG,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BtC,SAAgB,cAAgD,EAC9D,MACA,aACA,aACA,SACA,sBAAsB,OACtB,YACA,WACoC;CACpC,MAAM,EAAE,oBAAoB,yBAAyB,YAAY;CACjE,MAAM,aAAa,MAAM,OAAO,QAAQ;CACxC,MAAM,iBAAiB,MAAM,cACrB,wBAAwB,KAAK,EACnC,CAAC,KAAK,CACP;AAED,OAAM,gBAAgB;AACpB,aAAW,UAAU;IACpB,CAAC,QAAQ,CAAC;AAEb,OAAM,gBAAgB;EACpB,IAAI,cAAc;AAElB,GAAM,YAAY;GAChB,MAAM,EAAE,MAAM,MAAM,OAAO;AAC3B,OAAI,YACF;AAGF,sBAAmB;IACjB,YAAY;KACV,MAAM;KACN;KACA;KACA;KACA;KACA,YAAY,EAAE,aAAa,WAAW;KACvC;IACD,UAAU,SAAS,WAAW,QAAQ,KAA8B;IACrE,CAAC;MACA;AAEJ,eAAa;AACX,iBAAc;AACd,wBAAqB,eAAe;;IAErC;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@super_studio/ecforce-ai-agent-react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-canary.0",
|
|
4
4
|
"main": "./dist/index.mjs",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.mts",
|
|
@@ -24,14 +24,21 @@
|
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"react": ">=16",
|
|
27
|
-
"react-dom": ">=16"
|
|
27
|
+
"react-dom": ">=16",
|
|
28
|
+
"zod": ">=4"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"zod": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
28
34
|
},
|
|
29
35
|
"devDependencies": {
|
|
30
36
|
"@types/node": "22.14.0",
|
|
31
37
|
"@types/react": "19.2.7",
|
|
32
38
|
"@types/react-dom": "19.2.3",
|
|
33
39
|
"react": "19.2.4",
|
|
34
|
-
"react-dom": "19.2.4"
|
|
40
|
+
"react-dom": "19.2.4",
|
|
41
|
+
"zod": "4.1.12"
|
|
35
42
|
},
|
|
36
43
|
"scripts": {
|
|
37
44
|
"dev": "tsdown --watch",
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
+
import { PROD_CHATBOT_URL } from "../lib/constants";
|
|
2
|
+
|
|
1
3
|
type AiIconProps = {
|
|
2
4
|
width?: number;
|
|
3
5
|
height?: number;
|
|
4
6
|
};
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
const AI_ICON_GIF_URL = new URL(
|
|
9
|
+
"/images/ai-icon.gif",
|
|
10
|
+
PROD_CHATBOT_URL,
|
|
11
|
+
).toString();
|
|
12
|
+
|
|
13
|
+
export function AiIcon({ width = 24, height = 24 }: AiIconProps) {
|
|
7
14
|
return (
|
|
8
|
-
<
|
|
15
|
+
<img
|
|
16
|
+
src={AI_ICON_GIF_URL}
|
|
9
17
|
width={width}
|
|
10
18
|
height={height}
|
|
11
|
-
|
|
12
|
-
fill="currentColor"
|
|
19
|
+
alt=""
|
|
13
20
|
aria-hidden="true"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
clipRule="evenodd"
|
|
18
|
-
d="M9.2 13.704s3.7-.812 4.325-3.74c.624-2.93.76-3.364 1.112-3.364.352 0 .42.309.5.618.08.309.51 2.276.613 2.745.102.47.783 2.93 3.712 3.616 2.93.686 3.338.697 3.338 1.12 0 .459-2.361.859-3.338 1.122-.976.263-3.042 1.007-3.587 3.49-.534 2.482-.681 3.489-1.238 3.489-.556 0-.545-.584-.613-.87-.068-.286-.34-1.819-.5-2.494-.147-.675-1.044-2.837-2.962-3.363-1.93-.527-3.962-.847-3.962-1.373s2.134-.824 2.6-.995ZM2.462 4.613s1.785-.39 2.08-1.798c.296-1.409.365-1.615.535-1.615.17 0 .205.149.239.298s.25 1.088.296 1.317c.045.229.375 1.409 1.785 1.74 1.41.333 1.603.333 1.603.54 0 .217-1.137.412-1.603.538-.466.126-1.467.48-1.729 1.683C5.407 8.507 5.338 9 5.078 9c-.262 0-.262-.286-.296-.424-.035-.137-.16-.882-.24-1.202-.079-.321-.5-1.363-1.432-1.615-.932-.252-1.91-.413-1.91-.665 0-.252 1.023-.4 1.25-.48h.012Z"
|
|
19
|
-
/>
|
|
20
|
-
</svg>
|
|
21
|
+
draggable={false}
|
|
22
|
+
style={{ display: "block" }}
|
|
23
|
+
/>
|
|
21
24
|
);
|
|
22
25
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React from "react";
|
|
4
|
+
import type { RegisteredClientTool } from "../../lib/client-tools";
|
|
4
5
|
import {
|
|
5
|
-
type SendAppMessagePayload,
|
|
6
6
|
useChatbotFrameHandler,
|
|
7
7
|
type InitProps,
|
|
8
8
|
type MCP,
|
|
9
|
+
type SendAppMessagePayload,
|
|
9
10
|
} from "./use-chatbot-frame-handler";
|
|
10
11
|
import { useChatbotWindowStates } from "./use-chatbot-window-states";
|
|
11
12
|
|
|
@@ -34,6 +35,10 @@ type ChatbotContextType = {
|
|
|
34
35
|
setSessionToken: (sessionToken: string) => void;
|
|
35
36
|
/** アプリからメッセージを送信 */
|
|
36
37
|
sendAppMessage: (payload: SendAppMessagePayload) => void;
|
|
38
|
+
/** Client Toolを登録 */
|
|
39
|
+
registerClientTool: (tool: RegisteredClientTool) => void;
|
|
40
|
+
/** Client Toolを解除 */
|
|
41
|
+
unregisterClientTool: (name: string) => void;
|
|
37
42
|
/** チャットボットが準備できたか */
|
|
38
43
|
isReady: boolean;
|
|
39
44
|
/** チャットボットが初期化されたか */
|
|
@@ -49,6 +54,34 @@ const ChatbotContext = React.createContext<ChatbotContextType | undefined>(
|
|
|
49
54
|
undefined,
|
|
50
55
|
);
|
|
51
56
|
|
|
57
|
+
/**
|
|
58
|
+
* `ChatbotProvider` が提供するチャットボット状態と操作 API を取得します。
|
|
59
|
+
*
|
|
60
|
+
* @returns チャットボットの表示状態、初期化 API、Client Tool の登録 API を含むコンテキスト値。
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* import { useChatbot } from "@super_studio/ecforce-ai-agent-react";
|
|
65
|
+
*
|
|
66
|
+
* export function OpenChatButton() {
|
|
67
|
+
* const { open, setOpen, sendAppMessage } = useChatbot();
|
|
68
|
+
*
|
|
69
|
+
* return (
|
|
70
|
+
* <button
|
|
71
|
+
* type="button"
|
|
72
|
+
* onClick={() => {
|
|
73
|
+
* setOpen(!open);
|
|
74
|
+
* sendAppMessage({
|
|
75
|
+
* message: "EC Forceの注文状況を確認したいです",
|
|
76
|
+
* });
|
|
77
|
+
* }}
|
|
78
|
+
* >
|
|
79
|
+
* Open chat
|
|
80
|
+
* </button>
|
|
81
|
+
* );
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
52
85
|
export function useChatbot() {
|
|
53
86
|
const context = React.useContext(ChatbotContext);
|
|
54
87
|
if (!context) {
|
|
@@ -76,6 +109,8 @@ export function ChatbotProvider({ children }: ChatbotProviderProps) {
|
|
|
76
109
|
setAppName,
|
|
77
110
|
setSessionToken,
|
|
78
111
|
sendAppMessage,
|
|
112
|
+
registerClientTool,
|
|
113
|
+
unregisterClientTool,
|
|
79
114
|
init,
|
|
80
115
|
isReady,
|
|
81
116
|
isInitialized,
|
|
@@ -96,6 +131,8 @@ export function ChatbotProvider({ children }: ChatbotProviderProps) {
|
|
|
96
131
|
setIsFullScreen,
|
|
97
132
|
setSessionToken,
|
|
98
133
|
sendAppMessage,
|
|
134
|
+
registerClientTool,
|
|
135
|
+
unregisterClientTool,
|
|
99
136
|
init,
|
|
100
137
|
setMcps,
|
|
101
138
|
setAppName,
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React from "react";
|
|
4
|
+
import type {
|
|
5
|
+
ClientToolDefinition,
|
|
6
|
+
ClientToolExecuteRequest,
|
|
7
|
+
RegisteredClientTool,
|
|
8
|
+
} from "../../lib/client-tools";
|
|
4
9
|
|
|
5
10
|
export type IframeMessage =
|
|
6
11
|
| { type: "CHATBOT_READY" }
|
|
@@ -10,7 +15,10 @@ export type IframeMessage =
|
|
|
10
15
|
| { type: "SHRINK_CHATBOT" }
|
|
11
16
|
| { type: "FULLSCREEN_CHATBOT" }
|
|
12
17
|
| { type: "EXIT_FULLSCREEN_CHATBOT" }
|
|
13
|
-
| { type: "RELOAD_CHATBOT" }
|
|
18
|
+
| { type: "RELOAD_CHATBOT" }
|
|
19
|
+
| ({
|
|
20
|
+
type: "EXECUTE_CLIENT_TOOL";
|
|
21
|
+
} & ClientToolExecuteRequest);
|
|
14
22
|
|
|
15
23
|
export type MCP = {
|
|
16
24
|
name: string;
|
|
@@ -25,16 +33,24 @@ export type InitProps = {
|
|
|
25
33
|
};
|
|
26
34
|
|
|
27
35
|
export type AppMessageAttachment = {
|
|
36
|
+
/** 添付ファイルの表示名です。 */
|
|
28
37
|
fileName: string;
|
|
38
|
+
/** 添付ファイルの MIME type です。 */
|
|
29
39
|
mediaType: string;
|
|
40
|
+
/** 添付ファイルを取得できる URL です。 */
|
|
30
41
|
url: string;
|
|
31
42
|
};
|
|
32
43
|
|
|
33
44
|
export type SendAppMessagePayload = {
|
|
45
|
+
/** チャット上に表示するタイトルです。 */
|
|
34
46
|
title?: string;
|
|
47
|
+
/** ユーザーに表示するメッセージ本文です。 */
|
|
35
48
|
message: string;
|
|
49
|
+
/** UI に表示せず、裏側のコンテキストとして渡すメッセージです。 */
|
|
36
50
|
hiddenMessage?: string;
|
|
51
|
+
/** 新規チャットを開始してから送るかを指定します。 */
|
|
37
52
|
startOnNewChat?: boolean;
|
|
53
|
+
/** メッセージと一緒に渡す添付ファイル一覧です。 */
|
|
38
54
|
attachments?: AppMessageAttachment[];
|
|
39
55
|
};
|
|
40
56
|
|
|
@@ -60,23 +76,86 @@ export type ParentMessage =
|
|
|
60
76
|
| {
|
|
61
77
|
type: "appMessage";
|
|
62
78
|
payload: SendAppMessagePayload;
|
|
79
|
+
}
|
|
80
|
+
| {
|
|
81
|
+
type: "registerClientTools";
|
|
82
|
+
tools: ClientToolDefinition[];
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
type: "unregisterClientTools";
|
|
86
|
+
names: string[];
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
type: "clientToolResult";
|
|
90
|
+
callId: string;
|
|
91
|
+
result?: unknown;
|
|
92
|
+
error?: string;
|
|
63
93
|
};
|
|
64
94
|
|
|
95
|
+
function serializeError(error: unknown) {
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
return error.message;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (typeof error === "string") {
|
|
101
|
+
return error;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return "Unknown client tool error";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type UseChatbotFrameHandlerProps = {
|
|
108
|
+
/** チャットボットの開閉状態を更新します。 */
|
|
109
|
+
setOpen: (open: boolean) => void;
|
|
110
|
+
/** 展開状態を更新します。 */
|
|
111
|
+
setIsExpanded: (expanded: boolean) => void;
|
|
112
|
+
/** フルスクリーン状態を更新します。 */
|
|
113
|
+
setIsFullScreen: (fullScreen: boolean) => void;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* iframe と親アプリ間のメッセージ送受信を管理し、チャットボット制御 API をまとめます。
|
|
118
|
+
*
|
|
119
|
+
* @returns iframe 初期化、メッセージ送信、Client Tool 登録に使うハンドラ一式を返します。
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* export function FrameBridge() {
|
|
124
|
+
* const [open, setOpen] = React.useState(false);
|
|
125
|
+
* const [isExpanded, setIsExpanded] = React.useState(false);
|
|
126
|
+
* const [isFullScreen, setIsFullScreen] = React.useState(false);
|
|
127
|
+
* const { setIframeEl, init, sendAppMessage, isReady } =
|
|
128
|
+
* useChatbotFrameHandler({
|
|
129
|
+
* setOpen,
|
|
130
|
+
* setIsExpanded,
|
|
131
|
+
* setIsFullScreen,
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* React.useEffect(() => {
|
|
135
|
+
* if (isReady) {
|
|
136
|
+
* init({ appName: "EC Force" });
|
|
137
|
+
* sendAppMessage({ message: "在庫を確認したいです" });
|
|
138
|
+
* }
|
|
139
|
+
* }, [init, isReady, sendAppMessage]);
|
|
140
|
+
*
|
|
141
|
+
* return <iframe ref={setIframeEl} src="https://example.com/embed" />;
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
65
145
|
export function useChatbotFrameHandler({
|
|
66
146
|
setOpen,
|
|
67
147
|
setIsExpanded,
|
|
68
148
|
setIsFullScreen,
|
|
69
|
-
}: {
|
|
70
|
-
setOpen: (open: boolean) => void;
|
|
71
|
-
setIsExpanded: (expanded: boolean) => void;
|
|
72
|
-
setIsFullScreen: (fullScreen: boolean) => void;
|
|
73
|
-
}) {
|
|
149
|
+
}: UseChatbotFrameHandlerProps) {
|
|
74
150
|
const [iframeEl, setIframeEl] = React.useState<HTMLIFrameElement | null>(
|
|
75
151
|
null,
|
|
76
152
|
);
|
|
77
153
|
const [isReady, setIsReady] = React.useState(false);
|
|
78
154
|
const [isInitialized, setIsInitialized] = React.useState(false);
|
|
79
155
|
const pendingAppMessageRef = React.useRef<SendAppMessagePayload | null>(null);
|
|
156
|
+
const clientToolRegistryRef = React.useRef(
|
|
157
|
+
new Map<string, RegisteredClientTool>(),
|
|
158
|
+
);
|
|
80
159
|
|
|
81
160
|
// helper to post message to the iframe
|
|
82
161
|
const postMessage = React.useCallback(
|
|
@@ -158,6 +237,54 @@ export function useChatbotFrameHandler({
|
|
|
158
237
|
flushPendingAppMessage();
|
|
159
238
|
}, [flushPendingAppMessage]);
|
|
160
239
|
|
|
240
|
+
const registerClientTool = React.useCallback(
|
|
241
|
+
(tool: RegisteredClientTool) => {
|
|
242
|
+
clientToolRegistryRef.current.set(tool.definition.name, tool);
|
|
243
|
+
if (!isReady || !isInitialized) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
postMessage({
|
|
248
|
+
type: "registerClientTools",
|
|
249
|
+
tools: [tool.definition],
|
|
250
|
+
});
|
|
251
|
+
},
|
|
252
|
+
[isInitialized, isReady, postMessage],
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const unregisterClientTool = React.useCallback(
|
|
256
|
+
(name: string) => {
|
|
257
|
+
const deleted = clientToolRegistryRef.current.delete(name);
|
|
258
|
+
if (!deleted || !isReady || !isInitialized) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
postMessage({
|
|
263
|
+
type: "unregisterClientTools",
|
|
264
|
+
names: [name],
|
|
265
|
+
});
|
|
266
|
+
},
|
|
267
|
+
[isInitialized, isReady, postMessage],
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
React.useEffect(() => {
|
|
271
|
+
if (!isReady || !isInitialized) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const tools = [...clientToolRegistryRef.current.values()].map(
|
|
276
|
+
(tool) => tool.definition,
|
|
277
|
+
);
|
|
278
|
+
if (tools.length === 0) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
postMessage({
|
|
283
|
+
type: "registerClientTools",
|
|
284
|
+
tools,
|
|
285
|
+
});
|
|
286
|
+
}, [isInitialized, isReady, postMessage]);
|
|
287
|
+
|
|
161
288
|
// Initialize and setup listeners
|
|
162
289
|
React.useEffect(() => {
|
|
163
290
|
const iframe = iframeEl;
|
|
@@ -199,6 +326,36 @@ export function useChatbotFrameHandler({
|
|
|
199
326
|
// reload iframe
|
|
200
327
|
iframe.src = iframe.src;
|
|
201
328
|
break;
|
|
329
|
+
case "EXECUTE_CLIENT_TOOL": {
|
|
330
|
+
const { callId, name, args } = event.data;
|
|
331
|
+
const tool = clientToolRegistryRef.current.get(name);
|
|
332
|
+
if (!tool) {
|
|
333
|
+
postMessage({
|
|
334
|
+
type: "clientToolResult",
|
|
335
|
+
callId,
|
|
336
|
+
error: `Client tool not found: ${name}`,
|
|
337
|
+
});
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
void (async () => {
|
|
342
|
+
try {
|
|
343
|
+
const result = await tool.execute(args);
|
|
344
|
+
postMessage({
|
|
345
|
+
type: "clientToolResult",
|
|
346
|
+
callId,
|
|
347
|
+
result,
|
|
348
|
+
});
|
|
349
|
+
} catch (error) {
|
|
350
|
+
postMessage({
|
|
351
|
+
type: "clientToolResult",
|
|
352
|
+
callId,
|
|
353
|
+
error: serializeError(error),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
})();
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
202
359
|
}
|
|
203
360
|
};
|
|
204
361
|
|
|
@@ -222,6 +379,8 @@ export function useChatbotFrameHandler({
|
|
|
222
379
|
setAppName,
|
|
223
380
|
setSessionToken,
|
|
224
381
|
sendAppMessage,
|
|
382
|
+
registerClientTool,
|
|
383
|
+
unregisterClientTool,
|
|
225
384
|
setIframeEl,
|
|
226
385
|
isReady,
|
|
227
386
|
isInitialized,
|
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* チャットボット UI の開閉・展開・フルスクリーン状態を管理します。
|
|
7
|
+
*
|
|
8
|
+
* @returns 初回オープン判定と、各表示状態、その更新関数を返します。
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* export function ChatbotWindowController() {
|
|
13
|
+
* const { open, setOpen, isExpanded, setIsExpanded } =
|
|
14
|
+
* useChatbotWindowStates();
|
|
15
|
+
*
|
|
16
|
+
* return (
|
|
17
|
+
* <button
|
|
18
|
+
* type="button"
|
|
19
|
+
* onClick={() => {
|
|
20
|
+
* setOpen(!open);
|
|
21
|
+
* setIsExpanded(!isExpanded);
|
|
22
|
+
* }}
|
|
23
|
+
* >
|
|
24
|
+
* Toggle window
|
|
25
|
+
* </button>
|
|
26
|
+
* );
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
5
30
|
export function useChatbotWindowStates() {
|
|
6
31
|
const [hasOpened, setHasOpened] = React.useState(false);
|
|
7
32
|
const [open, setOpen] = React.useState(false);
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import type { z } from "zod";
|
|
5
|
+
import { useChatbot } from "../components/provider/chatbot-provider";
|
|
6
|
+
|
|
7
|
+
export type ClientToolJsonSchema = Record<string, unknown>;
|
|
8
|
+
const CLIENT_TOOL_NAME_PREFIX = "page_";
|
|
9
|
+
|
|
10
|
+
export type ClientToolDefinition = {
|
|
11
|
+
name: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
description: string;
|
|
14
|
+
summary?: string;
|
|
15
|
+
requireConfirmation?: boolean;
|
|
16
|
+
parameters: ClientToolJsonSchema;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ClientToolExecuteRequest = {
|
|
20
|
+
callId: string;
|
|
21
|
+
name: string;
|
|
22
|
+
args: unknown;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type RegisteredClientTool = {
|
|
26
|
+
definition: ClientToolDefinition;
|
|
27
|
+
execute: (args: unknown) => Promise<unknown> | unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type UseClientToolOptions<TParameters extends z.ZodTypeAny> = {
|
|
31
|
+
/** ツール名です。`page_`のプレフィックスが自動で追加されます。 */
|
|
32
|
+
name: string;
|
|
33
|
+
/** UI上に表示するツール名です。 */
|
|
34
|
+
displayName: string;
|
|
35
|
+
/** LLMが見れるツールの詳細説明です。 */
|
|
36
|
+
description: string;
|
|
37
|
+
/** ユーザーに表示するツールの要約です。 */
|
|
38
|
+
summary?: string;
|
|
39
|
+
/** 実行前にユーザー確認を要求するかを指定します。 */
|
|
40
|
+
requireConfirmation?: boolean;
|
|
41
|
+
/** ツールの引数を定義するZodスキーマです。 */
|
|
42
|
+
parameters: TParameters;
|
|
43
|
+
/** 検証済み引数を受け取り、ツール本体の処理を実行します。 */
|
|
44
|
+
execute: (args: z.output<TParameters>) => Promise<unknown> | unknown;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function normalizeClientToolName(name: string) {
|
|
48
|
+
if (name.startsWith(CLIENT_TOOL_NAME_PREFIX)) {
|
|
49
|
+
return name;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return `${CLIENT_TOOL_NAME_PREFIX}${name}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* チャットボットから呼び出せるクライアントツールを登録します。
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* import { z } from "zod";
|
|
61
|
+
* import { useClientTool } from "@super_studio/ecforce-ai-agent-react";
|
|
62
|
+
*
|
|
63
|
+
* export function InventoryToolRegistration() {
|
|
64
|
+
* useClientTool({
|
|
65
|
+
* name: "lookup_inventory",
|
|
66
|
+
* displayName: "在庫確認",
|
|
67
|
+
* description: "商品コードから在庫数を取得します。",
|
|
68
|
+
* summary: "商品在庫を返します。",
|
|
69
|
+
* parameters: z.object({
|
|
70
|
+
* sku: z.string().describe("商品コード"),
|
|
71
|
+
* }),
|
|
72
|
+
* execute: async ({ sku }) => {
|
|
73
|
+
* return { sku, available: true };
|
|
74
|
+
* },
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* return null;
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function useClientTool<TParameters extends z.ZodTypeAny>({
|
|
82
|
+
name,
|
|
83
|
+
displayName,
|
|
84
|
+
description,
|
|
85
|
+
summary,
|
|
86
|
+
requireConfirmation = false,
|
|
87
|
+
parameters,
|
|
88
|
+
execute,
|
|
89
|
+
}: UseClientToolOptions<TParameters>) {
|
|
90
|
+
const { registerClientTool, unregisterClientTool } = useChatbot();
|
|
91
|
+
const executeRef = React.useRef(execute);
|
|
92
|
+
const normalizedName = React.useMemo(
|
|
93
|
+
() => normalizeClientToolName(name),
|
|
94
|
+
[name],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
React.useEffect(() => {
|
|
98
|
+
executeRef.current = execute;
|
|
99
|
+
}, [execute]);
|
|
100
|
+
|
|
101
|
+
React.useEffect(() => {
|
|
102
|
+
let isCancelled = false;
|
|
103
|
+
|
|
104
|
+
void (async () => {
|
|
105
|
+
const { z } = await import("zod");
|
|
106
|
+
if (isCancelled) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
registerClientTool({
|
|
111
|
+
definition: {
|
|
112
|
+
name: normalizedName,
|
|
113
|
+
displayName,
|
|
114
|
+
description,
|
|
115
|
+
summary,
|
|
116
|
+
requireConfirmation,
|
|
117
|
+
parameters: z.toJSONSchema(parameters),
|
|
118
|
+
},
|
|
119
|
+
execute: (args) => executeRef.current(args as z.output<TParameters>),
|
|
120
|
+
});
|
|
121
|
+
})();
|
|
122
|
+
|
|
123
|
+
return () => {
|
|
124
|
+
isCancelled = true;
|
|
125
|
+
unregisterClientTool(normalizedName);
|
|
126
|
+
};
|
|
127
|
+
}, [
|
|
128
|
+
description,
|
|
129
|
+
displayName,
|
|
130
|
+
normalizedName,
|
|
131
|
+
parameters,
|
|
132
|
+
registerClientTool,
|
|
133
|
+
requireConfirmation,
|
|
134
|
+
summary,
|
|
135
|
+
unregisterClientTool,
|
|
136
|
+
]);
|
|
137
|
+
}
|