@luna-editor/engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/Player.d.ts +3 -0
  2. package/dist/Player.js +336 -0
  3. package/dist/atoms/screen-size.d.ts +22 -0
  4. package/dist/atoms/screen-size.js +8 -0
  5. package/dist/components/BacklogUI.d.ts +2 -0
  6. package/dist/components/BacklogUI.js +115 -0
  7. package/dist/components/ConversationLogUI.d.ts +2 -0
  8. package/dist/components/ConversationLogUI.js +115 -0
  9. package/dist/components/DebugControls.d.ts +12 -0
  10. package/dist/components/DebugControls.js +5 -0
  11. package/dist/components/DialogueBox.d.ts +2 -0
  12. package/dist/components/DialogueBox.js +28 -0
  13. package/dist/components/EndScreen.d.ts +8 -0
  14. package/dist/components/EndScreen.js +5 -0
  15. package/dist/components/GameScreen.d.ts +9 -0
  16. package/dist/components/GameScreen.js +111 -0
  17. package/dist/components/OverlayUI.d.ts +13 -0
  18. package/dist/components/OverlayUI.js +21 -0
  19. package/dist/components/PluginComponentProvider.d.ts +14 -0
  20. package/dist/components/PluginComponentProvider.js +24 -0
  21. package/dist/constants/screen-size.d.ts +3 -0
  22. package/dist/constants/screen-size.js +6 -0
  23. package/dist/contexts/DataContext.d.ts +24 -0
  24. package/dist/contexts/DataContext.js +101 -0
  25. package/dist/hooks/useBacklog.d.ts +14 -0
  26. package/dist/hooks/useBacklog.js +82 -0
  27. package/dist/hooks/useConversationLog.d.ts +14 -0
  28. package/dist/hooks/useConversationLog.js +82 -0
  29. package/dist/hooks/usePlayerLogic.d.ts +21 -0
  30. package/dist/hooks/usePlayerLogic.js +145 -0
  31. package/dist/hooks/usePluginAPI.d.ts +19 -0
  32. package/dist/hooks/usePluginAPI.js +42 -0
  33. package/dist/hooks/usePluginEvents.d.ts +14 -0
  34. package/dist/hooks/usePluginEvents.js +197 -0
  35. package/dist/hooks/usePreloadImages.d.ts +2 -0
  36. package/dist/hooks/usePreloadImages.js +56 -0
  37. package/dist/hooks/useScreenSize.d.ts +89 -0
  38. package/dist/hooks/useScreenSize.js +87 -0
  39. package/dist/hooks/useTypewriter.d.ts +11 -0
  40. package/dist/hooks/useTypewriter.js +56 -0
  41. package/dist/hooks/useUIVisibility.d.ts +9 -0
  42. package/dist/hooks/useUIVisibility.js +19 -0
  43. package/dist/hooks/useVoice.d.ts +4 -0
  44. package/dist/hooks/useVoice.js +21 -0
  45. package/dist/index.d.ts +10 -0
  46. package/dist/index.js +9 -0
  47. package/dist/plugin/PluginManager.d.ts +108 -0
  48. package/dist/plugin/PluginManager.js +851 -0
  49. package/dist/plugin/luna-react.d.ts +41 -0
  50. package/dist/plugin/luna-react.js +99 -0
  51. package/dist/sdk.d.ts +512 -0
  52. package/dist/sdk.js +64 -0
  53. package/dist/types.d.ts +186 -0
  54. package/dist/types.js +2 -0
  55. package/dist/utils/attributeNormalizer.d.ts +5 -0
  56. package/dist/utils/attributeNormalizer.js +53 -0
  57. package/dist/utils/facePositionCalculator.d.ts +29 -0
  58. package/dist/utils/facePositionCalculator.js +127 -0
  59. package/package.json +55 -0
@@ -0,0 +1,3 @@
1
+ import type React from "react";
2
+ import type { PlayerProps } from "./types";
3
+ export declare const Player: React.FC<PlayerProps>;
package/dist/Player.js ADDED
@@ -0,0 +1,336 @@
1
+ "use client";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
12
+ import { clsx } from "clsx";
13
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
+ import { useScreenSizeAtom } from "./atoms/screen-size";
15
+ import { DialogueBox } from "./components/DialogueBox";
16
+ import { EndScreen } from "./components/EndScreen";
17
+ import { GameScreen } from "./components/GameScreen";
18
+ import { OverlayUI } from "./components/OverlayUI";
19
+ import { PluginComponentProvider } from "./components/PluginComponentProvider";
20
+ import { DataProvider } from "./contexts/DataContext";
21
+ import { useBacklog } from "./hooks/useBacklog";
22
+ import { usePlayerLogic } from "./hooks/usePlayerLogic";
23
+ import { setGlobalUIAPI } from "./hooks/usePluginAPI";
24
+ import { usePluginEvents } from "./hooks/usePluginEvents";
25
+ import { usePreloadImages } from "./hooks/usePreloadImages";
26
+ import { useTypewriter } from "./hooks/useTypewriter";
27
+ import { PluginManager } from "./plugin/PluginManager";
28
+ import { ComponentType } from "./sdk";
29
+ export const Player = ({ scenario, settings, plugins = [], onEnd, onScenarioEnd, onScenarioStart, onScenarioCancelled, className, autoplay = false, }) => {
30
+ var _a;
31
+ const pluginManagerRef = useRef(new PluginManager());
32
+ // グローバルUIAPIを初期化(プラグインコンポーネントから使用可能にする)
33
+ useEffect(() => {
34
+ const uiAPI = pluginManagerRef.current.getUIAPI();
35
+ setGlobalUIAPI(uiAPI, pluginManagerRef.current);
36
+ }, []);
37
+ // 画面サイズの初期化
38
+ const [, setScreenSize] = useScreenSizeAtom();
39
+ useEffect(() => {
40
+ // クライアントサイドでのみ実行
41
+ if (typeof window === "undefined")
42
+ return;
43
+ setScreenSize({ width: window.innerWidth, height: window.innerHeight });
44
+ // リサイズ監視
45
+ const handleResize = () => {
46
+ setScreenSize({
47
+ width: window.innerWidth,
48
+ height: window.innerHeight,
49
+ });
50
+ };
51
+ window.addEventListener("resize", handleResize);
52
+ return () => window.removeEventListener("resize", handleResize);
53
+ }, [setScreenSize]);
54
+ // 表示可能なブロックのインデックスを事前計算
55
+ const displayableBlockIndices = useMemo(() => {
56
+ const supportedBlockTypes = ["dialogue", "narration"];
57
+ return scenario.blocks
58
+ .map((block, index) => ({ block, index }))
59
+ .filter(({ block }) => supportedBlockTypes.includes(block.blockType))
60
+ .map(({ index }) => index);
61
+ }, [scenario.blocks]);
62
+ const [state, setState] = useState({
63
+ currentBlockIndex: 0,
64
+ isPlaying: autoplay,
65
+ isEnded: false,
66
+ });
67
+ // 画像を事前読み込み
68
+ const imagesLoaded = usePreloadImages(scenario);
69
+ // プラグインの読み込み状態
70
+ const [pluginsLoaded, setPluginsLoaded] = useState(false);
71
+ // プラグインの読み込み
72
+ useEffect(() => {
73
+ const loadPlugins = () => __awaiter(void 0, void 0, void 0, function* () {
74
+ console.log("Loading plugins:", plugins);
75
+ setPluginsLoaded(false);
76
+ for (const plugin of plugins) {
77
+ console.log("Loading plugin:", plugin.packageName);
78
+ yield pluginManagerRef.current.loadPlugin(plugin.packageName, plugin.bundleUrl, plugin.config);
79
+ }
80
+ // プラグイン初期化フック - すべてのプラグインの読み込み完了後に実行
81
+ console.log("Calling onInit hook");
82
+ pluginManagerRef.current.callHook("onInit");
83
+ // DialogueBoxの初期状態を表示に設定
84
+ pluginManagerRef.current.showUI(ComponentType.DialogueBox);
85
+ setPluginsLoaded(true);
86
+ console.log("All plugins loaded successfully");
87
+ });
88
+ loadPlugins();
89
+ return () => {
90
+ const manager = pluginManagerRef.current;
91
+ manager.cleanup();
92
+ };
93
+ }, [plugins]);
94
+ // 初回レンダリング完了フラグ
95
+ const [isFirstRenderComplete, setIsFirstRenderComplete] = useState(false);
96
+ // シナリオ開始コールバック(後でcurrentBlockが定義された後に実行)
97
+ const [hasStarted, setHasStarted] = useState(false);
98
+ // スタイル要素の登録
99
+ useEffect(() => {
100
+ if (isFirstRenderComplete) {
101
+ // DOM要素の登録
102
+ const speakerNameElement = document.querySelector("[data-speaker-name-element]");
103
+ const dialogueElement = document.querySelector("[data-dialogue-element]");
104
+ // data-character-id属性を持つ最初のキャラクター要素を取得
105
+ const characterSpriteElement = document.querySelector("[data-character-id]");
106
+ const gameScreenElement = document.querySelector(".h-screen.w-full");
107
+ console.log("🔍 [StyleAPI] Element registration:", {
108
+ speakerNameElement: !!speakerNameElement,
109
+ dialogueElement: !!dialogueElement,
110
+ characterSpriteElement: !!characterSpriteElement,
111
+ gameScreenElement: !!gameScreenElement,
112
+ });
113
+ if (speakerNameElement) {
114
+ pluginManagerRef.current.registerStyleElement("speakerName", speakerNameElement);
115
+ }
116
+ if (dialogueElement) {
117
+ pluginManagerRef.current.registerStyleElement("dialogueBox", dialogueElement);
118
+ pluginManagerRef.current.registerStyleElement("scenarioBlockContent", dialogueElement);
119
+ }
120
+ if (characterSpriteElement) {
121
+ pluginManagerRef.current.registerStyleElement("characterSprite", characterSpriteElement);
122
+ }
123
+ if (gameScreenElement) {
124
+ pluginManagerRef.current.registerStyleElement("gameScreen", gameScreenElement);
125
+ pluginManagerRef.current.registerStyleElement("background", gameScreenElement);
126
+ }
127
+ }
128
+ }, [isFirstRenderComplete]);
129
+ const { displayText, isTyping, skipTyping, startTyping } = useTypewriter({
130
+ speed: 80,
131
+ });
132
+ // 現在の表示可能なブロックを取得
133
+ const currentBlock = displayableBlockIndices[state.currentBlockIndex] !== undefined
134
+ ? scenario.blocks[displayableBlockIndices[state.currentBlockIndex]]
135
+ : undefined;
136
+ // usePlayerLogicに渡す実際のブロックインデックス
137
+ const actualBlockIndex = (_a = displayableBlockIndices[state.currentBlockIndex]) !== null && _a !== void 0 ? _a : 0;
138
+ // バックログ機能
139
+ const backlog = useBacklog({
140
+ scenario,
141
+ currentBlockIndex: actualBlockIndex,
142
+ currentBlock: currentBlock || null,
143
+ });
144
+ // ダイアログ表示とアクションノード実行のuseEffectを分離
145
+ useEffect(() => {
146
+ if (currentBlock) {
147
+ startTyping(currentBlock.content || "");
148
+ }
149
+ }, [currentBlock, startTyping]);
150
+ // 画像読み込み完了後、GameScreenがマウントされてから表示する
151
+ useEffect(() => {
152
+ if (imagesLoaded && currentBlock && !isFirstRenderComplete) {
153
+ // requestAnimationFrameを使用してレンダリング完了を待つ
154
+ requestAnimationFrame(() => {
155
+ setIsFirstRenderComplete(true);
156
+ });
157
+ }
158
+ }, [imagesLoaded, currentBlock, isFirstRenderComplete]);
159
+ // シナリオ開始コールバック(currentBlock定義後)
160
+ useEffect(() => {
161
+ if (isFirstRenderComplete && !hasStarted && currentBlock) {
162
+ setHasStarted(true);
163
+ onScenarioStart === null || onScenarioStart === void 0 ? void 0 : onScenarioStart();
164
+ }
165
+ }, [isFirstRenderComplete, hasStarted, currentBlock, onScenarioStart]);
166
+ const { handleNext: handleNextInternal, handlePrevious: handlePreviousInternal, togglePlay, // eslint-disable-line @typescript-eslint/no-unused-vars
167
+ restart: restartInternal, displayedCharacters, } = usePlayerLogic({
168
+ state: Object.assign(Object.assign({}, state), { currentBlockIndex: actualBlockIndex }),
169
+ setState: (newState) => {
170
+ if (typeof newState === "function") {
171
+ setState((prev) => {
172
+ var _a;
173
+ const updated = newState(Object.assign(Object.assign({}, prev), { currentBlockIndex: (_a = displayableBlockIndices[prev.currentBlockIndex]) !== null && _a !== void 0 ? _a : 0 }));
174
+ // 実際のインデックスから表示可能インデックスに変換
175
+ const nextDisplayableIndex = displayableBlockIndices.findIndex((idx) => idx >= updated.currentBlockIndex);
176
+ return Object.assign(Object.assign({}, updated), { currentBlockIndex: nextDisplayableIndex !== -1
177
+ ? nextDisplayableIndex
178
+ : prev.currentBlockIndex });
179
+ });
180
+ }
181
+ else {
182
+ // 直接オブジェクトが渡された場合(restart時など)
183
+ setState(newState);
184
+ }
185
+ },
186
+ scenario,
187
+ isTyping,
188
+ currentBlock: currentBlock !== null && currentBlock !== void 0 ? currentBlock : scenario.blocks[0],
189
+ skipTyping,
190
+ onEnd,
191
+ onScenarioEnd,
192
+ autoplay,
193
+ });
194
+ // プラグインイベント処理
195
+ const realBlockIndex = currentBlock
196
+ ? scenario.blocks.indexOf(currentBlock)
197
+ : 0;
198
+ usePluginEvents({
199
+ pluginManager: pluginManagerRef.current,
200
+ currentBlock,
201
+ displayedCharacters,
202
+ blockIndex: state.currentBlockIndex,
203
+ isFirstRenderComplete,
204
+ allBlocks: scenario.blocks,
205
+ realBlockIndex,
206
+ pluginsLoaded,
207
+ });
208
+ // 初期履歴構築(シナリオ開始時)
209
+ useEffect(() => {
210
+ if (isFirstRenderComplete && actualBlockIndex >= 0) {
211
+ backlog.buildInitialHistory(actualBlockIndex);
212
+ }
213
+ }, [isFirstRenderComplete, actualBlockIndex, backlog]);
214
+ // ハンドラーをラップして表示可能インデックスで動作するように
215
+ const handleNext = useCallback(() => {
216
+ if (state.currentBlockIndex < displayableBlockIndices.length - 1) {
217
+ handleNextInternal();
218
+ }
219
+ else {
220
+ setState((prev) => (Object.assign(Object.assign({}, prev), { isEnded: true, isPlaying: false })));
221
+ onEnd === null || onEnd === void 0 ? void 0 : onEnd();
222
+ onScenarioEnd === null || onScenarioEnd === void 0 ? void 0 : onScenarioEnd();
223
+ }
224
+ }, [
225
+ state.currentBlockIndex,
226
+ displayableBlockIndices.length,
227
+ handleNextInternal,
228
+ onEnd,
229
+ onScenarioEnd,
230
+ ]);
231
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
232
+ const handlePrevious = useCallback(() => {
233
+ if (state.currentBlockIndex > 0) {
234
+ handlePreviousInternal();
235
+ }
236
+ }, [state.currentBlockIndex, handlePreviousInternal]);
237
+ // restartをラップして、firstRenderCompleteもリセット
238
+ const restart = useCallback(() => {
239
+ // リスタート時はシナリオがキャンセルされたとみなす
240
+ if (hasStarted && !state.isEnded) {
241
+ onScenarioCancelled === null || onScenarioCancelled === void 0 ? void 0 : onScenarioCancelled();
242
+ }
243
+ setIsFirstRenderComplete(false);
244
+ setHasStarted(false);
245
+ restartInternal();
246
+ }, [hasStarted, state.isEnded, onScenarioCancelled, restartInternal]);
247
+ // DataContext の構築 - displayedCharactersが必要なため usePlayerLogic の後に配置
248
+ const dataContext = useMemo(() => {
249
+ var _a, _b;
250
+ return ({
251
+ playback: {
252
+ currentBlockIndex: actualBlockIndex,
253
+ totalBlocks: scenario.blocks.length,
254
+ scenarioId: scenario.id,
255
+ scenarioName: scenario.name,
256
+ currentBlock: currentBlock || null,
257
+ displayText,
258
+ isTyping,
259
+ displayedCharacters,
260
+ },
261
+ backlog: {
262
+ entries: backlog.logs,
263
+ totalEntries: backlog.logs.length,
264
+ addLogEntry: backlog.addLogEntry,
265
+ clearLogs: backlog.clearLogs,
266
+ },
267
+ settings: {
268
+ aspectRatio: (_a = settings === null || settings === void 0 ? void 0 : settings.aspectRatio) !== null && _a !== void 0 ? _a : "16:9",
269
+ bgObjectFit: (_b = settings === null || settings === void 0 ? void 0 : settings.bgObjectFit) !== null && _b !== void 0 ? _b : "contain",
270
+ textSpeed: 5,
271
+ autoPlaySpeed: 3,
272
+ bgmVolume: 0.8,
273
+ seVolume: 1.0,
274
+ voiceVolume: 1.0,
275
+ skipMode: "unread",
276
+ },
277
+ pluginAssets: {
278
+ getAssetUrl: (pluginName, filename) => {
279
+ return pluginManagerRef.current.getPluginAssetUrl(pluginName, filename);
280
+ },
281
+ },
282
+ });
283
+ }, [
284
+ actualBlockIndex,
285
+ scenario,
286
+ currentBlock,
287
+ displayText,
288
+ isTyping,
289
+ displayedCharacters,
290
+ backlog,
291
+ settings,
292
+ ]);
293
+ // マウスホイールとタッチジェスチャーを無効化
294
+ useEffect(() => {
295
+ const handleWheel = (e) => {
296
+ e.preventDefault();
297
+ };
298
+ const handleTouchMove = (e) => {
299
+ if (e.touches.length > 1) {
300
+ e.preventDefault();
301
+ }
302
+ };
303
+ const handleGestureStart = (e) => {
304
+ e.preventDefault();
305
+ };
306
+ // イベントリスナーを追加
307
+ document.addEventListener("wheel", handleWheel, { passive: false });
308
+ document.addEventListener("touchmove", handleTouchMove, { passive: false });
309
+ document.addEventListener("gesturestart", handleGestureStart);
310
+ return () => {
311
+ document.removeEventListener("wheel", handleWheel);
312
+ document.removeEventListener("touchmove", handleTouchMove);
313
+ document.removeEventListener("gesturestart", handleGestureStart);
314
+ };
315
+ }, []);
316
+ // アスペクト比を計算
317
+ const getAspectRatio = () => {
318
+ if (!(settings === null || settings === void 0 ? void 0 : settings.aspectRatio))
319
+ return "16/9";
320
+ const [width, height] = settings.aspectRatio.split(":").map(Number);
321
+ return `${width}/${height}`;
322
+ };
323
+ // 条件付きレンダリングを JSX で処理(フックの後、early return なし)
324
+ return (_jsxs(_Fragment, { children: [!currentBlock && !state.isEnded && (_jsx("div", { className: clsx("flex items-center justify-center p-8", className), children: _jsx("p", { className: "text-gray-500", children: "\u30B7\u30CA\u30EA\u30AA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093" }) })), state.isEnded && (_jsx(EndScreen, { scenarioName: scenario.name, onRestart: restart, className: className })), currentBlock && !state.isEnded && (_jsxs(_Fragment, { children: [(!imagesLoaded || !isFirstRenderComplete) && (_jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center z-50", className), style: {
325
+ touchAction: "none",
326
+ userSelect: "none",
327
+ WebkitUserSelect: "none",
328
+ } })), _jsx("div", { className: clsx("luna-player fixed inset-0 bg-black overflow-hidden flex items-center justify-center", className, (!imagesLoaded || !isFirstRenderComplete) && "opacity-0"), onClick: handleNext, style: {
329
+ touchAction: "none",
330
+ userSelect: "none",
331
+ WebkitUserSelect: "none",
332
+ }, children: _jsx("div", { className: "relative bg-white flex flex-col w-full overflow-hidden h-full", style: { aspectRatio: getAspectRatio() }, children: _jsxs(DataProvider, { data: dataContext, children: [_jsx("div", { className: "h-full", children: _jsx(GameScreen, { scenario: scenario, currentBlock: currentBlock, displayedCharacters: displayedCharacters }) }), _jsx(OverlayUI, { children: _jsxs("div", { className: "h-full w-full relative", children: [_jsx(PluginComponentProvider, { type: ComponentType.DialogueBox, pluginManager: pluginManagerRef.current, fallback: DialogueBox }), pluginManagerRef.current
333
+ .getRegisteredComponents()
334
+ .filter((type) => type !== ComponentType.DialogueBox) // DialogueBoxは既に上でレンダリング済み
335
+ .map((componentType) => (_jsx(PluginComponentProvider, { type: componentType, pluginManager: pluginManagerRef.current }, componentType)))] }) })] }) }) })] }))] }));
336
+ };
@@ -0,0 +1,22 @@
1
+ export declare const screenSizeAtom: import("jotai").PrimitiveAtom<{
2
+ width: number;
3
+ height: number;
4
+ }> & {
5
+ init: {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ };
10
+ export declare const useScreenSizeAtom: () => [{
11
+ width: number;
12
+ height: number;
13
+ }, (args_0: {
14
+ width: number;
15
+ height: number;
16
+ } | ((prev: {
17
+ width: number;
18
+ height: number;
19
+ }) => {
20
+ width: number;
21
+ height: number;
22
+ })) => void];
@@ -0,0 +1,8 @@
1
+ import { atom, useAtom } from "jotai";
2
+ export const screenSizeAtom = atom({
3
+ width: 0,
4
+ height: 0,
5
+ });
6
+ export const useScreenSizeAtom = () => {
7
+ return useAtom(screenSizeAtom);
8
+ };
@@ -0,0 +1,2 @@
1
+ import type React from "react";
2
+ export declare const BacklogUI: React.FC;
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useState } from "react";
3
+ import { useDataAPI } from "../contexts/DataContext";
4
+ export const BacklogUI = () => {
5
+ const dataAPI = useDataAPI();
6
+ const logs = dataAPI.get("backlog", "entries");
7
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
8
+ const openDialog = useCallback((e) => {
9
+ e.stopPropagation();
10
+ e.preventDefault();
11
+ setIsDialogOpen(true);
12
+ }, []);
13
+ const closeDialog = useCallback((e) => {
14
+ if (e) {
15
+ e.stopPropagation();
16
+ e.preventDefault();
17
+ }
18
+ setIsDialogOpen(false);
19
+ }, []);
20
+ const handleDialogBackdropClick = useCallback((e) => {
21
+ if (e.target === e.currentTarget) {
22
+ closeDialog();
23
+ }
24
+ }, [closeDialog]);
25
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: openDialog, style: {
26
+ position: "fixed",
27
+ top: "20px",
28
+ right: "20px",
29
+ width: "50px",
30
+ height: "50px",
31
+ background: "rgba(74, 144, 226, 0.9)",
32
+ border: "none",
33
+ borderRadius: "25px",
34
+ color: "white",
35
+ fontSize: "20px",
36
+ cursor: "pointer",
37
+ zIndex: 1000,
38
+ boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
39
+ transition: "all 0.3s ease",
40
+ }, onMouseOver: (e) => {
41
+ e.stopPropagation();
42
+ e.currentTarget.style.background = "rgba(74, 144, 226, 1)";
43
+ e.currentTarget.style.transform = "scale(1.1)";
44
+ }, onMouseOut: (e) => {
45
+ e.stopPropagation();
46
+ e.currentTarget.style.background = "rgba(74, 144, 226, 0.9)";
47
+ e.currentTarget.style.transform = "scale(1)";
48
+ }, title: "\u4F1A\u8A71\u30ED\u30B0\u3092\u8868\u793A", children: "\uD83D\uDCDA" }), isDialogOpen && (_jsx("div", { style: {
49
+ position: "fixed",
50
+ top: 0,
51
+ left: 0,
52
+ right: 0,
53
+ bottom: 0,
54
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
55
+ display: "flex",
56
+ justifyContent: "center",
57
+ alignItems: "center",
58
+ zIndex: 10000,
59
+ }, onClick: handleDialogBackdropClick, children: _jsxs("div", { style: {
60
+ width: "80vw",
61
+ maxWidth: "800px",
62
+ maxHeight: "80vh",
63
+ background: "rgba(0, 0, 0, 0.9)",
64
+ border: "1px solid #333",
65
+ borderRadius: "8px",
66
+ padding: "20px",
67
+ color: "white",
68
+ fontFamily: "sans-serif",
69
+ boxShadow: "0 4px 20px rgba(0,0,0,0.5)",
70
+ }, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { style: {
71
+ display: "flex",
72
+ justifyContent: "space-between",
73
+ alignItems: "center",
74
+ marginBottom: "16px",
75
+ borderBottom: "1px solid #333",
76
+ paddingBottom: "12px",
77
+ }, children: [_jsx("h3", { style: {
78
+ margin: 0,
79
+ fontSize: "1.2em",
80
+ color: "#4A90E2",
81
+ }, children: "\uD83D\uDCDA \u4F1A\u8A71\u30ED\u30B0" }), _jsx("button", { type: "button", onClick: closeDialog, style: {
82
+ background: "#666",
83
+ border: "none",
84
+ color: "white",
85
+ padding: "6px 12px",
86
+ borderRadius: "4px",
87
+ cursor: "pointer",
88
+ }, children: "\u2715 \u9589\u3058\u308B" })] }), _jsx("div", { style: {
89
+ maxHeight: "60vh",
90
+ overflowY: "auto",
91
+ marginBottom: "16px",
92
+ scrollbarWidth: "thin",
93
+ scrollbarColor: "#666 transparent",
94
+ }, children: logs.length > 0 ? (logs.map((log) => (_jsxs("div", { style: {
95
+ marginBottom: "12px",
96
+ padding: "12px",
97
+ background: "rgba(255,255,255,0.1)",
98
+ borderRadius: "4px",
99
+ borderLeft: "3px solid #4A90E2",
100
+ }, children: [_jsx("div", { style: {
101
+ marginBottom: "6px",
102
+ fontSize: "1em",
103
+ }, children: log.speakerName ? (_jsx("strong", { children: log.speakerName })) : (_jsx("em", { children: "\u30CA\u30EC\u30FC\u30B7\u30E7\u30F3" })) }), _jsx("div", { style: {
104
+ fontSize: "1em",
105
+ lineHeight: "1.5",
106
+ }, children: log.content || "(内容なし)" })] }, log.id)))) : (_jsx("div", { style: {
107
+ textAlign: "center",
108
+ color: "#888",
109
+ padding: "20px",
110
+ }, children: "\u307E\u3060\u4F1A\u8A71\u30ED\u30B0\u304C\u3042\u308A\u307E\u305B\u3093" })) }), _jsxs("div", { style: {
111
+ textAlign: "center",
112
+ fontSize: "0.8em",
113
+ color: "#888",
114
+ }, children: [logs.length, "\u4EF6\u306E\u4F1A\u8A71\u3092\u8868\u793A\u4E2D"] })] }) }))] }));
115
+ };
@@ -0,0 +1,2 @@
1
+ import type React from "react";
2
+ export declare const ConversationLogUI: React.FC;
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useState } from "react";
3
+ import { useDataAPI } from "../contexts/DataContext";
4
+ export const ConversationLogUI = () => {
5
+ const dataAPI = useDataAPI();
6
+ const logs = dataAPI.get("conversationLog", "entries");
7
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
8
+ const openDialog = useCallback((e) => {
9
+ e.stopPropagation();
10
+ e.preventDefault();
11
+ setIsDialogOpen(true);
12
+ }, []);
13
+ const closeDialog = useCallback((e) => {
14
+ if (e) {
15
+ e.stopPropagation();
16
+ e.preventDefault();
17
+ }
18
+ setIsDialogOpen(false);
19
+ }, []);
20
+ const handleDialogBackdropClick = useCallback((e) => {
21
+ if (e.target === e.currentTarget) {
22
+ closeDialog();
23
+ }
24
+ }, [closeDialog]);
25
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: openDialog, style: {
26
+ position: "fixed",
27
+ top: "20px",
28
+ right: "20px",
29
+ width: "50px",
30
+ height: "50px",
31
+ background: "rgba(74, 144, 226, 0.9)",
32
+ border: "none",
33
+ borderRadius: "25px",
34
+ color: "white",
35
+ fontSize: "20px",
36
+ cursor: "pointer",
37
+ zIndex: 1000,
38
+ boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
39
+ transition: "all 0.3s ease",
40
+ }, onMouseOver: (e) => {
41
+ e.stopPropagation();
42
+ e.currentTarget.style.background = "rgba(74, 144, 226, 1)";
43
+ e.currentTarget.style.transform = "scale(1.1)";
44
+ }, onMouseOut: (e) => {
45
+ e.stopPropagation();
46
+ e.currentTarget.style.background = "rgba(74, 144, 226, 0.9)";
47
+ e.currentTarget.style.transform = "scale(1)";
48
+ }, title: "\u4F1A\u8A71\u30ED\u30B0\u3092\u8868\u793A", children: "\uD83D\uDCDA" }), isDialogOpen && (_jsx("div", { style: {
49
+ position: "fixed",
50
+ top: 0,
51
+ left: 0,
52
+ right: 0,
53
+ bottom: 0,
54
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
55
+ display: "flex",
56
+ justifyContent: "center",
57
+ alignItems: "center",
58
+ zIndex: 10000,
59
+ }, onClick: handleDialogBackdropClick, children: _jsxs("div", { style: {
60
+ width: "80vw",
61
+ maxWidth: "800px",
62
+ maxHeight: "80vh",
63
+ background: "rgba(0, 0, 0, 0.9)",
64
+ border: "1px solid #333",
65
+ borderRadius: "8px",
66
+ padding: "20px",
67
+ color: "white",
68
+ fontFamily: "sans-serif",
69
+ boxShadow: "0 4px 20px rgba(0,0,0,0.5)",
70
+ }, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { style: {
71
+ display: "flex",
72
+ justifyContent: "space-between",
73
+ alignItems: "center",
74
+ marginBottom: "16px",
75
+ borderBottom: "1px solid #333",
76
+ paddingBottom: "12px",
77
+ }, children: [_jsx("h3", { style: {
78
+ margin: 0,
79
+ fontSize: "1.2em",
80
+ color: "#4A90E2",
81
+ }, children: "\uD83D\uDCDA \u4F1A\u8A71\u30ED\u30B0" }), _jsx("button", { type: "button", onClick: closeDialog, style: {
82
+ background: "#666",
83
+ border: "none",
84
+ color: "white",
85
+ padding: "6px 12px",
86
+ borderRadius: "4px",
87
+ cursor: "pointer",
88
+ }, children: "\u2715 \u9589\u3058\u308B" })] }), _jsx("div", { style: {
89
+ maxHeight: "60vh",
90
+ overflowY: "auto",
91
+ marginBottom: "16px",
92
+ scrollbarWidth: "thin",
93
+ scrollbarColor: "#666 transparent",
94
+ }, children: logs.length > 0 ? (logs.map((log) => (_jsxs("div", { style: {
95
+ marginBottom: "12px",
96
+ padding: "12px",
97
+ background: "rgba(255,255,255,0.1)",
98
+ borderRadius: "4px",
99
+ borderLeft: "3px solid #4A90E2",
100
+ }, children: [_jsx("div", { style: {
101
+ marginBottom: "6px",
102
+ fontSize: "1em",
103
+ }, children: log.speakerName ? (_jsx("strong", { children: log.speakerName })) : (_jsx("em", { children: "\u30CA\u30EC\u30FC\u30B7\u30E7\u30F3" })) }), _jsx("div", { style: {
104
+ fontSize: "1em",
105
+ lineHeight: "1.5",
106
+ }, children: log.content || "(内容なし)" })] }, log.id)))) : (_jsx("div", { style: {
107
+ textAlign: "center",
108
+ color: "#888",
109
+ padding: "20px",
110
+ }, children: "\u307E\u3060\u4F1A\u8A71\u30ED\u30B0\u304C\u3042\u308A\u307E\u305B\u3093" })) }), _jsxs("div", { style: {
111
+ textAlign: "center",
112
+ fontSize: "0.8em",
113
+ color: "#888",
114
+ }, children: [logs.length, "\u4EF6\u306E\u4F1A\u8A71\u3092\u8868\u793A\u4E2D"] })] }) }))] }));
115
+ };
@@ -0,0 +1,12 @@
1
+ import type React from "react";
2
+ interface DebugControlsProps {
3
+ currentIndex: number;
4
+ totalBlocks: number;
5
+ isPlaying: boolean;
6
+ canGoPrevious: boolean;
7
+ onPrevious: () => void;
8
+ onTogglePlay: () => void;
9
+ onNext: () => void;
10
+ }
11
+ export declare const DebugControls: React.FC<DebugControlsProps>;
12
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronRight, Pause, Play } from "lucide-react";
3
+ export const DebugControls = ({ currentIndex, totalBlocks, isPlaying, canGoPrevious, onPrevious, onTogglePlay, onNext, }) => {
4
+ return (_jsxs("div", { className: "absolute top-4 right-4 bg-black/50 p-2 rounded-lg flex items-center space-x-2 z-20 pointer-events-auto", children: [_jsx("button", { type: "button", onClick: onPrevious, disabled: !canGoPrevious, className: "p-1 rounded text-white disabled:opacity-30 disabled:cursor-not-allowed hover:bg-white/20 transition-colors", children: _jsx(ChevronRight, { className: "w-4 h-4 rotate-180" }) }), _jsx("button", { type: "button", onClick: onTogglePlay, className: "p-1 rounded text-white hover:bg-white/20 transition-colors", children: isPlaying ? (_jsx(Pause, { className: "w-4 h-4" })) : (_jsx(Play, { className: "w-4 h-4" })) }), _jsx("button", { type: "button", onClick: onNext, className: "p-1 rounded text-white hover:bg-white/20 transition-colors", children: _jsx(ChevronRight, { className: "w-4 h-4" }) }), _jsxs("div", { className: "text-xs text-white/80 ml-2", children: [currentIndex + 1, "/", totalBlocks] })] }));
5
+ };
@@ -0,0 +1,2 @@
1
+ import type React from "react";
2
+ export declare const DialogueBox: React.FC;