@super_studio/ecforce-ai-agent-react 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1 +1,308 @@
1
- TODO
1
+ # セットアップ
2
+
3
+ このドキュメントでは、`@super_studio/ecforce-ai-agent-react` と `@super_studio/ecforce-ai-agent-server` を使って、Webアプリに AI Agent のチャット UI とサーバー連携を組み込む手順を説明します。
4
+
5
+ 想定構成は以下です。
6
+
7
+ - フロントエンドで `@super_studio/ecforce-ai-agent-react` を使ってチャット UI を表示する
8
+ - バックエンドで `@super_studio/ecforce-ai-agent-server` を使ってセッショントークンを発行する
9
+ - 必要に応じて MCP エンドポイントでトークンを検証し、アプリ固有のツールを公開する
10
+
11
+ ## 1. インストール
12
+
13
+ ```bash
14
+ pnpm add @super_studio/ecforce-ai-agent-react @super_studio/ecforce-ai-agent-server
15
+ ```
16
+
17
+ ## 2. 環境変数を設定する
18
+
19
+ 通常の本番環境 (`https://agent.ec-force.com`) に接続するだけであれば、`NEXT_PUBLIC_CHATBOT_URL` は不要です。
20
+
21
+ `@super_studio/ecforce-ai-agent-server` を使う場合は、API キーとして `AI_AGENT_API_KEY` を設定してください。
22
+
23
+ MCP を実装する場合は、あわせて `MCP_TOKEN_SECRET` も必要です。
24
+
25
+ `AI_AGENT_API_KEY` と `MCP_TOKEN_SECRET` は AI チームから共有された値を使用してください。
26
+
27
+ ```env
28
+ AI_AGENT_API_KEY=your-api-key
29
+ MCP_TOKEN_SECRET=your-mcp-token-secret
30
+ ```
31
+
32
+ 開発環境やプレビュー環境など、本番以外の AI Agent に接続したい場合のみ、接続先 URL を設定します。
33
+
34
+ 開発用のエンドポイントは以下です。
35
+
36
+ ```env
37
+ NEXT_PUBLIC_CHATBOT_URL=https://dev-agent.ec-force.com
38
+ AI_AGENT_API_ENDPOINT=https://dev-agent.ec-force.com
39
+ ```
40
+
41
+ - `NEXT_PUBLIC_CHATBOT_URL`: フロントエンドの `ChatbotSheet` / `ChatbotFrame` を dev / preview 環境へ向けたい場合に使います
42
+ - `AI_AGENT_API_ENDPOINT`: サーバー側の `createClient()` を dev / preview 環境へ向けたい場合に使います
43
+ - `AI_AGENT_API_KEY`: サーバー SDK から API を呼ぶために必須です
44
+ - `MCP_TOKEN_SECRET`: MCP トークンの署名・検証に使います。MCP を実装する場合に必須です
45
+
46
+ ## 3. `@super_studio/ecforce-ai-agent-react` のセットアップ
47
+
48
+ ### 3-1. スタイルを読み込む
49
+
50
+ グローバル CSS で、パッケージが提供するスタイルを読み込みます。
51
+
52
+ ```css
53
+ @import "@super_studio/ecforce-ai-agent-react/preset.css";
54
+ @import "@super_studio/ecforce-ai-agent-react/chatbot-sheet.css";
55
+ ```
56
+
57
+ ### 3-2. アプリを `ChatbotProvider` でラップする
58
+
59
+ チャット UI や `useChatbot()` を使う場合は、アプリ全体または必要な範囲を `ChatbotProvider` で囲みます。
60
+
61
+ ```tsx
62
+ "use client";
63
+
64
+ import { ChatbotProvider } from "@super_studio/ecforce-ai-agent-react";
65
+
66
+ type Props = {
67
+ children: React.ReactNode;
68
+ };
69
+
70
+ export function AppProviders({ children }: Props) {
71
+ return <ChatbotProvider>{children}</ChatbotProvider>;
72
+ }
73
+ ```
74
+
75
+ ### 3-3. チャット UI を表示する
76
+
77
+ `ChatbotSheet` にアプリ名、セッション取得関数を渡します。通常は本番環境へ接続されるため、URL 指定は不要です。
78
+
79
+ ```tsx
80
+ "use client";
81
+
82
+ import { ChatbotSheet } from "@super_studio/ecforce-ai-agent-react";
83
+
84
+ export function Chatbot() {
85
+ return (
86
+ <ChatbotSheet
87
+ appName="your-app-name"
88
+ getSession={async () => {
89
+ const res = await fetch("/api/agent/session", {
90
+ method: "POST",
91
+ });
92
+
93
+ if (!res.ok) {
94
+ throw new Error("Failed to create AI agent session");
95
+ }
96
+
97
+ return (await res.json()) as {
98
+ token: string;
99
+ expiresAt: string;
100
+ };
101
+ }}
102
+ />
103
+ );
104
+ }
105
+ ```
106
+
107
+ #### 各 props の役割
108
+
109
+ - `url`: AI Agent サーバーのベース URL。dev / preview 環境に向けたい場合のみ指定します
110
+ - `appName`: 呼び出し元アプリを識別する名前
111
+ - `getSession`: AI Agent に接続するためのセッショントークンを返す関数
112
+
113
+ dev / preview 環境へ接続する場合のみ、以下のように `url` を指定してください。
114
+
115
+ ```tsx
116
+ <ChatbotSheet
117
+ url={process.env.NEXT_PUBLIC_CHATBOT_URL}
118
+ appName="your-app-name"
119
+ getSession={...}
120
+ />
121
+ ```
122
+
123
+ ### 3-4. アプリ側の状態や文脈を AI Agent に渡す
124
+
125
+ `useChatbot()` の `sendAppMessage()` を使うと、ホストアプリから AI Agent に対してプログラム経由でメッセージを送れます。
126
+
127
+ 典型的には、以下のような場面で使います。
128
+
129
+ - 現在表示中の商品、注文、顧客などを前提に AI へ依頼したい
130
+ - ユーザーが押したボタンに応じて、定型プロンプトをそのまま送信したい
131
+ - 画面には見せたくない補足情報を AI にだけ渡したい
132
+ - 画像やファイル URL を添付して、その内容を前提に会話を始めたい
133
+
134
+ `sendAppMessage()` を呼ぶと、チャットが閉じていても自動で開きます。チャット iframe の初期化がまだ終わっていない場合でも、メッセージは内部で一時保持され、準備完了後に送信されます。
135
+
136
+ ```tsx
137
+ "use client";
138
+
139
+ import { useChatbot } from "@super_studio/ecforce-ai-agent-react";
140
+
141
+ export function SendContextButton() {
142
+ const { sendAppMessage } = useChatbot();
143
+
144
+ return (
145
+ <button
146
+ onClick={() => {
147
+ sendAppMessage({
148
+ title: "売上ダッシュボードの要約",
149
+ message:
150
+ "現在の売上ダッシュボードを前提に、注目すべき変化を3つ挙げてください。",
151
+ hiddenMessage:
152
+ "dashboardId=dashboard_123, dashboardName=売上ダッシュボード",
153
+ });
154
+ }}
155
+ >
156
+ コンテキストを送信
157
+ </button>
158
+ );
159
+ }
160
+ ```
161
+
162
+ #### `sendAppMessage()` の payload
163
+
164
+ ```ts
165
+ type SendAppMessagePayload = {
166
+ title?: string;
167
+ message: string;
168
+ hiddenMessage?: string;
169
+ startOnNewChat?: boolean;
170
+ attachments?: {
171
+ fileName: string;
172
+ mediaType: string;
173
+ url: string;
174
+ }[];
175
+ };
176
+ ```
177
+
178
+ - `message`: AI Agent に実際に依頼したい本文です。必須です
179
+ - `title`: 依頼の要約タイトルです。UI 上でタスク名のように扱いたいときに使います
180
+ - `hiddenMessage`: ユーザーに見せなくてよい補足文脈です。内部 ID、画面状態、回答方針などを渡す用途に向いています
181
+ - `startOnNewChat`: `true` なら新しいチャットを開始して送信します。省略時は `true` です。現在の会話の続きに送りたい場合だけ `false` を指定します
182
+ - `attachments`: AI Agent に参照させたい添付ファイルです。公開 URL で取得できるファイルを渡してください
183
+
184
+ #### visible な依頼と hidden な文脈の使い分け
185
+
186
+ 基本的には、ユーザーが見ても自然な内容を `message` に書き、アプリ内部の補足情報を `hiddenMessage` に分けるのがおすすめです。
187
+
188
+ - `message`: 「この注文の要点を要約して」
189
+ - `hiddenMessage`: `orderId=ord_123`, `status=paid`, `customerTier=gold`
190
+
191
+ このように分けると、ユーザー向けの依頼文を保ちながら、AI 側には必要な文脈も渡せます。
192
+
193
+ #### 新しいチャットで送るか、現在の会話に続けるか
194
+
195
+ 新しい依頼を独立した会話として扱いたい場合は、デフォルトのまま `startOnNewChat: true` を使います。すでに進行中の会話に追加指示したい場合は `false` を指定します。
196
+
197
+ ```tsx
198
+ sendAppMessage({
199
+ title: "続きの依頼",
200
+ message: "最も良い案を、2行のヒーローコピーに言い換えてください。",
201
+ startOnNewChat: false,
202
+ });
203
+ ```
204
+
205
+ #### 添付ファイルを渡す例
206
+
207
+ ```tsx
208
+ sendAppMessage({
209
+ title: "バナー案のレビュー",
210
+ message: "添付画像を確認し、改善案を3つ提案してください。",
211
+ attachments: [
212
+ {
213
+ fileName: "campaign-banner.png",
214
+ mediaType: "image/png",
215
+ url: "https://example.com/campaign-banner.png",
216
+ },
217
+ ],
218
+ });
219
+ ```
220
+
221
+ これにより、AI Agent 側で現在の画面や対象データに応じた応答をしやすくなります。
222
+
223
+ ## 4. `@super_studio/ecforce-ai-agent-server` のセットアップ
224
+
225
+ ### 4-1. サーバー側で AI Agent セッションを発行する
226
+
227
+ フロントエンドの `getSession()` から呼ばれる API を用意し、`createClient()` を使って AI Agent サーバーへセッション作成を依頼します。
228
+
229
+ このとき、サーバー環境に `AI_AGENT_API_KEY` の設定が必要です。
230
+
231
+ 以下は Next.js App Router の Route Handler 例です。
232
+
233
+ ```ts
234
+ import { NextResponse } from "next/server";
235
+ import { createClient } from "@super_studio/ecforce-ai-agent-server";
236
+
237
+ const aiClient = createClient();
238
+
239
+ export async function POST() {
240
+ // ここは自分の認証基盤に置き換えてください
241
+ const currentUser = {
242
+ email: "user@example.com",
243
+ projectId: "project_123",
244
+ };
245
+
246
+ const session = await aiClient.internalChat.createSession({
247
+ email: currentUser.email,
248
+ projectId: currentUser.projectId,
249
+ });
250
+
251
+ return NextResponse.json(session);
252
+ }
253
+ ```
254
+
255
+ dev / preview 環境へ接続する場合は、`AI_AGENT_API_ENDPOINT` を設定するか、`createClient({ baseUrl: "..." })` を使って接続先を上書きしてください。
256
+
257
+ `getSession()` には、この API が返す以下の形式の値をそのまま返せば動作します。
258
+
259
+ ```ts
260
+ type AgentSession = {
261
+ token: string;
262
+ expiresAt: string;
263
+ };
264
+ ```
265
+
266
+ ### 4-2. MCP エンドポイントでトークンを検証する
267
+
268
+ アプリ固有のツールを AI Agent に公開する場合は、MCP エンドポイント側で受け取ったトークンを検証します。
269
+
270
+ このとき、サーバー環境に `MCP_TOKEN_SECRET` の設定が必要です。
271
+
272
+ `@super_studio/ecforce-ai-agent-server/mcp-auth` には、MCP トークンを扱うためのユーティリティが含まれています。
273
+
274
+ ```ts
275
+ import { getMcpToken } from "@super_studio/ecforce-ai-agent-server/mcp-auth";
276
+
277
+ export async function GET(req: Request) {
278
+ const mcpToken = getMcpToken(req);
279
+
280
+ if (!mcpToken) {
281
+ return new Response("Unauthorized", { status: 401 });
282
+ }
283
+
284
+ return new Response("ok");
285
+ }
286
+ ```
287
+
288
+ トークンの中身を利用したい場合は `decodeMCPToken()` を使います。
289
+
290
+ ```ts
291
+ import { decodeMCPToken } from "@super_studio/ecforce-ai-agent-server/mcp-auth";
292
+
293
+ export async function GET(req: Request) {
294
+ const authHeader = req.headers.get("authorization");
295
+ const token = authHeader?.replace(/^Bearer\s+/i, "");
296
+
297
+ if (!token) {
298
+ return new Response("Unauthorized", { status: 401 });
299
+ }
300
+
301
+ const payload = await decodeMCPToken(token);
302
+
303
+ return Response.json({
304
+ source: payload.source,
305
+ user: payload.user,
306
+ });
307
+ }
308
+ ```
@@ -0,0 +1,20 @@
1
+ import { PROD_CHATBOT_URL } from "../lib/constants.mjs";
2
+ import { jsx } from "react/jsx-runtime";
3
+
4
+ //#region src/components/ai-icon.tsx
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,
9
+ width,
10
+ height,
11
+ alt: "",
12
+ "aria-hidden": "true",
13
+ draggable: false,
14
+ style: { display: "block" }
15
+ });
16
+ }
17
+
18
+ //#endregion
19
+ export { AiIcon };
20
+ //# sourceMappingURL=ai-icon.mjs.map
@@ -0,0 +1 @@
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 +1 @@
1
- {"version":3,"file":"chatbot-sheet.d.mts","names":[],"sources":["../../src/components/chatbot-sheet.tsx"],"sourcesContent":[],"mappings":";;;;;KAeK,iBAAA,GAAoB;eACV,KAAA,CAAM;;AADhB,iBAIW,YAAA,CAJS;EAAA,UAAA;EAAA,GAAA;AACS,CAAb,EAGkC,iBAHrB,CAAA,EAGsC,kBAAA,CAAA,GAAA,CAAA,OAHtC"}
1
+ {"version":3,"file":"chatbot-sheet.d.mts","names":[],"sources":["../../src/components/chatbot-sheet.tsx"],"sourcesContent":[],"mappings":";;;;;KAcK,iBAAA,GAAoB;eACV,KAAA,CAAM;;AADhB,iBAIW,YAAA,CAJS;EAAA,UAAA;EAAA,GAAA;AACS,CAAb,EAGkC,iBAHrB,CAAA,EAGsC,kBAAA,CAAA,GAAA,CAAA,OAHtC"}
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { _objectSpread2 } from "../_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs";
4
- import { PROD_CHATBOT_URL } from "../lib/constants.mjs";
5
4
  import { ChatbotFrame } from "./chatbot-frame.mjs";
6
- import { LottieAiIcon } from "./lottie-ai-icon.mjs";
5
+ import { AiIcon } from "./ai-icon.mjs";
7
6
  import { _objectWithoutProperties } from "../_virtual/_@oxc-project_runtime@0.103.0/helpers/objectWithoutProperties.mjs";
8
7
  import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "./sheet.mjs";
9
8
  import { Tooltip } from "./tooltip.mjs";
@@ -18,12 +17,9 @@ function ChatbotSheet(_ref) {
18
17
  side: "top",
19
18
  align: "end",
20
19
  content: "AIに質問してみましょう",
21
- trigger: /* @__PURE__ */ jsx(SheetTrigger, { children: /* @__PURE__ */ jsx(LottieAiIcon, {
22
- src: `${PROD_CHATBOT_URL}/animations/ai-icon.v1.lottie`,
20
+ trigger: /* @__PURE__ */ jsx(SheetTrigger, { children: /* @__PURE__ */ jsx(AiIcon, {
23
21
  width: 24,
24
- height: 24,
25
- autoplay: true,
26
- loop: true
22
+ height: 24
27
23
  }) })
28
24
  }), /* @__PURE__ */ jsxs(SheetContent, {
29
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 { PROD_CHATBOT_URL } from \"@/lib/constants\";\nimport { ChatbotFrame, type ChatbotFrameProps } from \"./chatbot-frame\";\nimport { LottieAiIcon } from \"./lottie-ai-icon\";\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 <LottieAiIcon\n src={`${PROD_CHATBOT_URL}/animations/ai-icon.v1.lottie`}\n width={24}\n height={24}\n autoplay\n loop\n />\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":";;;;;;;;;;;;;mBAmB+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;GACC,KAAK,GAAG,iBAAiB;GACzB,OAAO;GACP,QAAQ;GACR;GACA;IACA,GACW;GAEjB,EACF,qBAAC;EAAa,OAAO;;GACnB,oBAAC,wBAAW,iBAAyB;GACrC,oBAAC,8BAAiB,iBAA+B;GACjD,oBAAC,iCAAiB,OAAS;;GACd,IACT"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@super_studio/ecforce-ai-agent-react",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "main": "./dist/index.mjs",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.mts",
@@ -0,0 +1,25 @@
1
+ import { PROD_CHATBOT_URL } from "../lib/constants";
2
+
3
+ type AiIconProps = {
4
+ width?: number;
5
+ height?: number;
6
+ };
7
+
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) {
14
+ return (
15
+ <img
16
+ src={AI_ICON_GIF_URL}
17
+ width={width}
18
+ height={height}
19
+ alt=""
20
+ aria-hidden="true"
21
+ draggable={false}
22
+ style={{ display: "block" }}
23
+ />
24
+ );
25
+ }
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import React from "react";
4
- import { PROD_CHATBOT_URL } from "@/lib/constants";
4
+ import { AiIcon } from "./ai-icon";
5
5
  import { ChatbotFrame, type ChatbotFrameProps } from "./chatbot-frame";
6
- import { LottieAiIcon } from "./lottie-ai-icon";
7
6
  import {
8
7
  Sheet,
9
8
  SheetContent,
@@ -26,13 +25,7 @@ export function ChatbotSheet({ sheetStyle, ...props }: ChatbotSheetProps) {
26
25
  content="AIに質問してみましょう"
27
26
  trigger={
28
27
  <SheetTrigger>
29
- <LottieAiIcon
30
- src={`${PROD_CHATBOT_URL}/animations/ai-icon.v1.lottie`}
31
- width={24}
32
- height={24}
33
- autoplay
34
- loop
35
- />
28
+ <AiIcon width={24} height={24} />
36
29
  </SheetTrigger>
37
30
  }
38
31
  />
@@ -1,58 +0,0 @@
1
- "use client";
2
-
3
- import * as React$1 from "react";
4
- import { jsx } from "react/jsx-runtime";
5
-
6
- //#region src/components/lottie-ai-icon.tsx
7
- function LottieAiIcon({ src, width = 18, height = 18, loop = true, autoplay = false, fallback }) {
8
- const containerRef = React$1.useRef(null);
9
- const [ready, setReady] = React$1.useState(false);
10
- React$1.useEffect(() => {
11
- if (typeof window === "undefined") return;
12
- if (!document.querySelector("script[data-dotlottie-wc]")) {
13
- const scriptEl = document.createElement("script");
14
- scriptEl.type = "module";
15
- scriptEl.async = true;
16
- scriptEl.src = "https://unpkg.com/@lottiefiles/dotlottie-wc@0.9.8/dist/dotlottie-wc.js";
17
- scriptEl.setAttribute("data-dotlottie-wc", "true");
18
- scriptEl.onload = () => setReady(true);
19
- scriptEl.onerror = () => setReady(false);
20
- document.head.appendChild(scriptEl);
21
- } else setReady(true);
22
- }, []);
23
- React$1.useEffect(() => {
24
- if (!ready || !containerRef.current) return;
25
- const container = containerRef.current;
26
- container.innerHTML = "";
27
- const player = document.createElement("dotlottie-wc");
28
- player.setAttribute("src", src);
29
- player.setAttribute("style", `width:${width}px;height:${height}px`);
30
- if (loop) player.setAttribute("loop", "");
31
- if (autoplay) player.setAttribute("autoplay", "");
32
- container.appendChild(player);
33
- return () => {
34
- container.innerHTML = "";
35
- };
36
- }, [
37
- ready,
38
- src,
39
- width,
40
- height,
41
- loop,
42
- autoplay
43
- ]);
44
- return /* @__PURE__ */ jsx("div", {
45
- ref: containerRef,
46
- "aria-hidden": "true",
47
- children: !ready && (fallback !== null && fallback !== void 0 ? fallback : /* @__PURE__ */ jsx("svg", {
48
- width,
49
- height,
50
- viewBox: "0 0 24 24",
51
- fill: "currentColor"
52
- }))
53
- });
54
- }
55
-
56
- //#endregion
57
- export { LottieAiIcon };
58
- //# sourceMappingURL=lottie-ai-icon.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lottie-ai-icon.mjs","names":["React"],"sources":["../../src/components/lottie-ai-icon.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\ntype LottieAiIconProps = {\n src: string;\n width?: number;\n height?: number;\n loop?: boolean;\n autoplay?: boolean;\n fallback?: React.ReactNode;\n};\n\nexport function LottieAiIcon({\n src,\n width = 18,\n height = 18,\n loop = true,\n autoplay = false,\n fallback,\n}: LottieAiIconProps) {\n const containerRef = React.useRef<HTMLDivElement>(null);\n const [ready, setReady] = React.useState(false);\n\n React.useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const existing = document.querySelector(\"script[data-dotlottie-wc]\");\n if (!existing) {\n const scriptEl = document.createElement(\"script\");\n scriptEl.type = \"module\";\n scriptEl.async = true;\n scriptEl.src =\n \"https://unpkg.com/@lottiefiles/dotlottie-wc@0.9.8/dist/dotlottie-wc.js\";\n scriptEl.setAttribute(\"data-dotlottie-wc\", \"true\");\n scriptEl.onload = () => setReady(true);\n scriptEl.onerror = () => setReady(false);\n document.head.appendChild(scriptEl);\n } else {\n setReady(true);\n }\n }, []);\n\n React.useEffect(() => {\n if (!ready || !containerRef.current) return;\n const container = containerRef.current;\n container.innerHTML = \"\";\n const player = document.createElement(\"dotlottie-wc\");\n player.setAttribute(\"src\", src);\n player.setAttribute(\"style\", `width:${width}px;height:${height}px`);\n if (loop) player.setAttribute(\"loop\", \"\");\n if (autoplay) player.setAttribute(\"autoplay\", \"\");\n container.appendChild(player);\n return () => {\n container.innerHTML = \"\";\n };\n }, [ready, src, width, height, loop, autoplay]);\n\n return (\n <div ref={containerRef} aria-hidden=\"true\">\n {!ready &&\n (fallback ?? (\n <svg\n width={width}\n height={height}\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;AAaA,SAAgB,aAAa,EAC3B,KACA,QAAQ,IACR,SAAS,IACT,OAAO,MACP,WAAW,OACX,YACoB;CACpB,MAAM,eAAeA,QAAM,OAAuB,KAAK;CACvD,MAAM,CAAC,OAAO,YAAYA,QAAM,SAAS,MAAM;AAE/C,SAAM,gBAAgB;AACpB,MAAI,OAAO,WAAW,YAAa;AAGnC,MAAI,CADa,SAAS,cAAc,4BAA4B,EACrD;GACb,MAAM,WAAW,SAAS,cAAc,SAAS;AACjD,YAAS,OAAO;AAChB,YAAS,QAAQ;AACjB,YAAS,MACP;AACF,YAAS,aAAa,qBAAqB,OAAO;AAClD,YAAS,eAAe,SAAS,KAAK;AACtC,YAAS,gBAAgB,SAAS,MAAM;AACxC,YAAS,KAAK,YAAY,SAAS;QAEnC,UAAS,KAAK;IAEf,EAAE,CAAC;AAEN,SAAM,gBAAgB;AACpB,MAAI,CAAC,SAAS,CAAC,aAAa,QAAS;EACrC,MAAM,YAAY,aAAa;AAC/B,YAAU,YAAY;EACtB,MAAM,SAAS,SAAS,cAAc,eAAe;AACrD,SAAO,aAAa,OAAO,IAAI;AAC/B,SAAO,aAAa,SAAS,SAAS,MAAM,YAAY,OAAO,IAAI;AACnE,MAAI,KAAM,QAAO,aAAa,QAAQ,GAAG;AACzC,MAAI,SAAU,QAAO,aAAa,YAAY,GAAG;AACjD,YAAU,YAAY,OAAO;AAC7B,eAAa;AACX,aAAU,YAAY;;IAEvB;EAAC;EAAO;EAAK;EAAO;EAAQ;EAAM;EAAS,CAAC;AAE/C,QACE,oBAAC;EAAI,KAAK;EAAc,eAAY;YACjC,CAAC,UACC,sDACC,oBAAC;GACQ;GACC;GACR,SAAQ;GACR,MAAK;IACL;GAEF"}
@@ -1,72 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- type LottieAiIconProps = {
6
- src: string;
7
- width?: number;
8
- height?: number;
9
- loop?: boolean;
10
- autoplay?: boolean;
11
- fallback?: React.ReactNode;
12
- };
13
-
14
- export function LottieAiIcon({
15
- src,
16
- width = 18,
17
- height = 18,
18
- loop = true,
19
- autoplay = false,
20
- fallback,
21
- }: LottieAiIconProps) {
22
- const containerRef = React.useRef<HTMLDivElement>(null);
23
- const [ready, setReady] = React.useState(false);
24
-
25
- React.useEffect(() => {
26
- if (typeof window === "undefined") return;
27
-
28
- const existing = document.querySelector("script[data-dotlottie-wc]");
29
- if (!existing) {
30
- const scriptEl = document.createElement("script");
31
- scriptEl.type = "module";
32
- scriptEl.async = true;
33
- scriptEl.src =
34
- "https://unpkg.com/@lottiefiles/dotlottie-wc@0.9.8/dist/dotlottie-wc.js";
35
- scriptEl.setAttribute("data-dotlottie-wc", "true");
36
- scriptEl.onload = () => setReady(true);
37
- scriptEl.onerror = () => setReady(false);
38
- document.head.appendChild(scriptEl);
39
- } else {
40
- setReady(true);
41
- }
42
- }, []);
43
-
44
- React.useEffect(() => {
45
- if (!ready || !containerRef.current) return;
46
- const container = containerRef.current;
47
- container.innerHTML = "";
48
- const player = document.createElement("dotlottie-wc");
49
- player.setAttribute("src", src);
50
- player.setAttribute("style", `width:${width}px;height:${height}px`);
51
- if (loop) player.setAttribute("loop", "");
52
- if (autoplay) player.setAttribute("autoplay", "");
53
- container.appendChild(player);
54
- return () => {
55
- container.innerHTML = "";
56
- };
57
- }, [ready, src, width, height, loop, autoplay]);
58
-
59
- return (
60
- <div ref={containerRef} aria-hidden="true">
61
- {!ready &&
62
- (fallback ?? (
63
- <svg
64
- width={width}
65
- height={height}
66
- viewBox="0 0 24 24"
67
- fill="currentColor"
68
- />
69
- ))}
70
- </div>
71
- );
72
- }