sa2kit 2.0.1 → 2.0.3

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 (278) hide show
  1. package/README.md +1 -1
  2. package/dist/CollisionBalls-BpHufX3H.d.mts +41 -0
  3. package/dist/CollisionBalls-BpHufX3H.d.ts +41 -0
  4. package/dist/ConfigService-BxK06xP6.d.mts +262 -0
  5. package/dist/ConfigService-BxK06xP6.d.ts +262 -0
  6. package/dist/UniversalFileService-BpvbZitV.d.mts +139 -0
  7. package/dist/UniversalFileService-GsP6D3Rc.d.ts +139 -0
  8. package/dist/audioDetection/index.d.mts +449 -0
  9. package/dist/audioDetection/index.d.ts +449 -0
  10. package/dist/audioDetection/index.js +1244 -0
  11. package/dist/audioDetection/index.js.map +1 -0
  12. package/dist/audioDetection/index.mjs +1227 -0
  13. package/dist/audioDetection/index.mjs.map +1 -0
  14. package/dist/auth/legacy/core/index.d.mts +42 -0
  15. package/dist/auth/legacy/core/index.d.ts +42 -0
  16. package/dist/auth/legacy/core/index.js +242 -0
  17. package/dist/auth/legacy/core/index.js.map +1 -0
  18. package/dist/auth/legacy/core/index.mjs +226 -0
  19. package/dist/auth/legacy/core/index.mjs.map +1 -0
  20. package/dist/auth/legacy/db/index.d.mts +5 -0
  21. package/dist/auth/legacy/db/index.d.ts +5 -0
  22. package/dist/auth/legacy/db/index.js +261 -0
  23. package/dist/auth/legacy/db/index.js.map +1 -0
  24. package/dist/auth/legacy/db/index.mjs +250 -0
  25. package/dist/auth/legacy/db/index.mjs.map +1 -0
  26. package/dist/auth/legacy/index.d.mts +5 -0
  27. package/dist/auth/legacy/index.d.ts +5 -0
  28. package/dist/auth/legacy/index.js +1107 -0
  29. package/dist/auth/legacy/index.js.map +1 -0
  30. package/dist/auth/legacy/index.mjs +1086 -0
  31. package/dist/auth/legacy/index.mjs.map +1 -0
  32. package/dist/auth/legacy/logic/index.d.mts +9 -0
  33. package/dist/auth/legacy/logic/index.d.ts +9 -0
  34. package/dist/auth/legacy/logic/index.js +194 -0
  35. package/dist/auth/legacy/logic/index.js.map +1 -0
  36. package/dist/auth/legacy/logic/index.mjs +187 -0
  37. package/dist/auth/legacy/logic/index.mjs.map +1 -0
  38. package/dist/auth/legacy/miniapp/index.d.mts +5 -0
  39. package/dist/auth/legacy/miniapp/index.d.ts +5 -0
  40. package/dist/auth/legacy/miniapp/index.js +506 -0
  41. package/dist/auth/legacy/miniapp/index.js.map +1 -0
  42. package/dist/auth/legacy/miniapp/index.mjs +487 -0
  43. package/dist/auth/legacy/miniapp/index.mjs.map +1 -0
  44. package/dist/auth/legacy/routes/index.d.mts +53 -0
  45. package/dist/auth/legacy/routes/index.d.ts +53 -0
  46. package/dist/auth/legacy/routes/index.js +278 -0
  47. package/dist/auth/legacy/routes/index.js.map +1 -0
  48. package/dist/auth/legacy/routes/index.mjs +271 -0
  49. package/dist/auth/legacy/routes/index.mjs.map +1 -0
  50. package/dist/auth/legacy/schema/index.d.mts +401 -0
  51. package/dist/auth/legacy/schema/index.d.ts +401 -0
  52. package/dist/auth/legacy/schema/index.js +50 -0
  53. package/dist/auth/legacy/schema/index.js.map +1 -0
  54. package/dist/auth/legacy/schema/index.mjs +44 -0
  55. package/dist/auth/legacy/schema/index.mjs.map +1 -0
  56. package/dist/auth/legacy/server/index.d.mts +13 -0
  57. package/dist/auth/legacy/server/index.d.ts +13 -0
  58. package/dist/auth/legacy/server/index.js +21 -0
  59. package/dist/auth/legacy/server/index.js.map +1 -0
  60. package/dist/auth/legacy/server/index.mjs +19 -0
  61. package/dist/auth/legacy/server/index.mjs.map +1 -0
  62. package/dist/auth/legacy/services/index.d.mts +40 -0
  63. package/dist/auth/legacy/services/index.d.ts +40 -0
  64. package/dist/auth/legacy/services/index.js +258 -0
  65. package/dist/auth/legacy/services/index.js.map +1 -0
  66. package/dist/auth/legacy/services/index.mjs +252 -0
  67. package/dist/auth/legacy/services/index.mjs.map +1 -0
  68. package/dist/auth/legacy/ui/miniapp/index.d.mts +10 -0
  69. package/dist/auth/legacy/ui/miniapp/index.d.ts +10 -0
  70. package/dist/auth/legacy/ui/miniapp/index.js +298 -0
  71. package/dist/auth/legacy/ui/miniapp/index.js.map +1 -0
  72. package/dist/auth/legacy/ui/miniapp/index.mjs +290 -0
  73. package/dist/auth/legacy/ui/miniapp/index.mjs.map +1 -0
  74. package/dist/auth/legacy/ui/web/index.d.mts +22 -0
  75. package/dist/auth/legacy/ui/web/index.d.ts +22 -0
  76. package/dist/auth/legacy/ui/web/index.js +899 -0
  77. package/dist/auth/legacy/ui/web/index.js.map +1 -0
  78. package/dist/auth/legacy/ui/web/index.mjs +889 -0
  79. package/dist/auth/legacy/ui/web/index.mjs.map +1 -0
  80. package/dist/auth/legacy/web/index.d.mts +5 -0
  81. package/dist/auth/legacy/web/index.d.ts +5 -0
  82. package/dist/auth/legacy/web/index.js +1107 -0
  83. package/dist/auth/legacy/web/index.js.map +1 -0
  84. package/dist/auth/legacy/web/index.mjs +1086 -0
  85. package/dist/auth/legacy/web/index.mjs.map +1 -0
  86. package/dist/auth/rn/index.d.mts +64 -0
  87. package/dist/auth/rn/index.d.ts +64 -0
  88. package/dist/auth/rn/index.js +765 -0
  89. package/dist/auth/rn/index.js.map +1 -0
  90. package/dist/auth/rn/index.mjs +754 -0
  91. package/dist/auth/rn/index.mjs.map +1 -0
  92. package/dist/base-api-client-ACKKt13v.d.mts +277 -0
  93. package/dist/base-api-client-ACKKt13v.d.ts +277 -0
  94. package/dist/boothVaultService-Cn4WPhjg.d.mts +83 -0
  95. package/dist/boothVaultService-Cn4WPhjg.d.ts +83 -0
  96. package/dist/business/index.d.mts +6 -0
  97. package/dist/business/index.d.ts +6 -0
  98. package/dist/business/index.js +1682 -0
  99. package/dist/business/index.js.map +1 -0
  100. package/dist/business/index.mjs +1675 -0
  101. package/dist/business/index.mjs.map +1 -0
  102. package/dist/calendar/index.d.mts +1325 -0
  103. package/dist/calendar/index.d.ts +1325 -0
  104. package/dist/calendar/index.js +5964 -0
  105. package/dist/calendar/index.js.map +1 -0
  106. package/dist/calendar/index.mjs +5878 -0
  107. package/dist/calendar/index.mjs.map +1 -0
  108. package/dist/calendar/routes/index.d.mts +118 -0
  109. package/dist/calendar/routes/index.d.ts +118 -0
  110. package/dist/calendar/routes/index.js +755 -0
  111. package/dist/calendar/routes/index.js.map +1 -0
  112. package/dist/calendar/routes/index.mjs +747 -0
  113. package/dist/calendar/routes/index.mjs.map +1 -0
  114. package/dist/components/index.d.mts +405 -0
  115. package/dist/components/index.d.ts +405 -0
  116. package/dist/components/index.js +2516 -0
  117. package/dist/components/index.js.map +1 -0
  118. package/dist/components/index.mjs +2396 -0
  119. package/dist/components/index.mjs.map +1 -0
  120. package/dist/drizzle-schema-BNhqj2AZ.d.mts +1114 -0
  121. package/dist/drizzle-schema-BNhqj2AZ.d.ts +1114 -0
  122. package/dist/festivalCard/index.d.mts +76 -0
  123. package/dist/festivalCard/index.d.ts +76 -0
  124. package/dist/festivalCard/index.js +1492 -0
  125. package/dist/festivalCard/index.js.map +1 -0
  126. package/dist/festivalCard/index.mjs +1475 -0
  127. package/dist/festivalCard/index.mjs.map +1 -0
  128. package/dist/festivalCard/routes/index.d.mts +42 -0
  129. package/dist/festivalCard/routes/index.d.ts +42 -0
  130. package/dist/festivalCard/routes/index.js +361 -0
  131. package/dist/festivalCard/routes/index.js.map +1 -0
  132. package/dist/festivalCard/routes/index.mjs +356 -0
  133. package/dist/festivalCard/routes/index.mjs.map +1 -0
  134. package/dist/festivalCard/server/index.d.mts +120 -0
  135. package/dist/festivalCard/server/index.d.ts +120 -0
  136. package/dist/festivalCard/server/index.js +272 -0
  137. package/dist/festivalCard/server/index.js.map +1 -0
  138. package/dist/festivalCard/server/index.mjs +265 -0
  139. package/dist/festivalCard/server/index.mjs.map +1 -0
  140. package/dist/festivalCardService-BFCRhJrq.d.ts +13 -0
  141. package/dist/festivalCardService-GriR2VMc.d.mts +13 -0
  142. package/dist/index-1Ag7IBXN.d.ts +144 -0
  143. package/dist/index-DNKZ7-R_.d.mts +184 -0
  144. package/dist/index-DNKZ7-R_.d.ts +184 -0
  145. package/dist/index-DSel44Ke.d.mts +93 -0
  146. package/dist/index-DSel44Ke.d.ts +93 -0
  147. package/dist/index-DdeZSeTJ.d.mts +144 -0
  148. package/dist/index-DrPcMJPc.d.mts +250 -0
  149. package/dist/index-DrPcMJPc.d.ts +250 -0
  150. package/dist/index.d.mts +5334 -0
  151. package/dist/index.d.ts +5334 -0
  152. package/dist/index.js +18809 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/index.mjs +18533 -0
  155. package/dist/index.mjs.map +1 -0
  156. package/dist/mikuContest/ui/web/index.d.mts +2 -0
  157. package/dist/mikuContest/ui/web/index.d.ts +2 -0
  158. package/dist/mikuContest/ui/web/index.js +353 -0
  159. package/dist/mikuContest/ui/web/index.js.map +1 -0
  160. package/dist/mikuContest/ui/web/index.mjs +343 -0
  161. package/dist/mikuContest/ui/web/index.mjs.map +1 -0
  162. package/dist/mikuFireworks3D/index.d.mts +268 -0
  163. package/dist/mikuFireworks3D/index.d.ts +268 -0
  164. package/dist/mikuFireworks3D/index.js +1267 -0
  165. package/dist/mikuFireworks3D/index.js.map +1 -0
  166. package/dist/mikuFireworks3D/index.mjs +1228 -0
  167. package/dist/mikuFireworks3D/index.mjs.map +1 -0
  168. package/dist/mikuFusionGame/index.d.mts +117 -0
  169. package/dist/mikuFusionGame/index.d.ts +117 -0
  170. package/dist/mikuFusionGame/index.js +1208 -0
  171. package/dist/mikuFusionGame/index.js.map +1 -0
  172. package/dist/mikuFusionGame/index.mjs +1195 -0
  173. package/dist/mikuFusionGame/index.mjs.map +1 -0
  174. package/dist/mmd/admin/index.d.mts +487 -0
  175. package/dist/mmd/admin/index.d.ts +487 -0
  176. package/dist/mmd/admin/index.js +1058 -0
  177. package/dist/mmd/admin/index.js.map +1 -0
  178. package/dist/mmd/admin/index.mjs +1027 -0
  179. package/dist/mmd/admin/index.mjs.map +1 -0
  180. package/dist/mmd/index.d.mts +2467 -0
  181. package/dist/mmd/index.d.ts +2467 -0
  182. package/dist/mmd/index.js +10119 -0
  183. package/dist/mmd/index.js.map +1 -0
  184. package/dist/mmd/index.mjs +10028 -0
  185. package/dist/mmd/index.mjs.map +1 -0
  186. package/dist/mmd/server/index.d.mts +139 -0
  187. package/dist/mmd/server/index.d.ts +139 -0
  188. package/dist/mmd/server/index.js +424 -0
  189. package/dist/mmd/server/index.js.map +1 -0
  190. package/dist/mmd/server/index.mjs +404 -0
  191. package/dist/mmd/server/index.mjs.map +1 -0
  192. package/dist/music/index.d.mts +74 -0
  193. package/dist/music/index.d.ts +74 -0
  194. package/dist/music/index.js +830 -0
  195. package/dist/music/index.js.map +1 -0
  196. package/dist/music/index.mjs +809 -0
  197. package/dist/music/index.mjs.map +1 -0
  198. package/dist/music/server/index.d.mts +1 -0
  199. package/dist/music/server/index.d.ts +1 -0
  200. package/dist/music/server/index.js +194 -0
  201. package/dist/music/server/index.js.map +1 -0
  202. package/dist/music/server/index.mjs +182 -0
  203. package/dist/music/server/index.mjs.map +1 -0
  204. package/dist/navigation/index.d.mts +93 -0
  205. package/dist/navigation/index.d.ts +93 -0
  206. package/dist/navigation/index.js +453 -0
  207. package/dist/navigation/index.js.map +1 -0
  208. package/dist/navigation/index.mjs +443 -0
  209. package/dist/navigation/index.mjs.map +1 -0
  210. package/dist/portfolio/index.d.mts +66 -0
  211. package/dist/portfolio/index.d.ts +66 -0
  212. package/dist/portfolio/index.js +736 -0
  213. package/dist/portfolio/index.js.map +1 -0
  214. package/dist/portfolio/index.mjs +724 -0
  215. package/dist/portfolio/index.mjs.map +1 -0
  216. package/dist/qqbot/server/index.d.mts +216 -0
  217. package/dist/qqbot/server/index.d.ts +216 -0
  218. package/dist/qqbot/server/index.js +394 -0
  219. package/dist/qqbot/server/index.js.map +1 -0
  220. package/dist/qqbot/server/index.mjs +385 -0
  221. package/dist/qqbot/server/index.mjs.map +1 -0
  222. package/dist/qqbot/ui/web/index.d.mts +10 -0
  223. package/dist/qqbot/ui/web/index.d.ts +10 -0
  224. package/dist/qqbot/ui/web/index.js +105 -0
  225. package/dist/qqbot/ui/web/index.js.map +1 -0
  226. package/dist/qqbot/ui/web/index.mjs +99 -0
  227. package/dist/qqbot/ui/web/index.mjs.map +1 -0
  228. package/dist/screenReceiver/index.d.mts +86 -0
  229. package/dist/screenReceiver/index.d.ts +86 -0
  230. package/dist/screenReceiver/index.js +281 -0
  231. package/dist/screenReceiver/index.js.map +1 -0
  232. package/dist/screenReceiver/index.mjs +273 -0
  233. package/dist/screenReceiver/index.mjs.map +1 -0
  234. package/dist/testYourself/admin/index.d.mts +58 -0
  235. package/dist/testYourself/admin/index.d.ts +58 -0
  236. package/dist/testYourself/admin/index.js +1009 -0
  237. package/dist/testYourself/admin/index.js.map +1 -0
  238. package/dist/testYourself/admin/index.mjs +1002 -0
  239. package/dist/testYourself/admin/index.mjs.map +1 -0
  240. package/dist/testYourself/index.d.mts +53 -0
  241. package/dist/testYourself/index.d.ts +53 -0
  242. package/dist/testYourself/index.js +2551 -0
  243. package/dist/testYourself/index.js.map +1 -0
  244. package/dist/testYourself/index.mjs +2531 -0
  245. package/dist/testYourself/index.mjs.map +1 -0
  246. package/dist/testYourself/server/index.d.mts +1029 -0
  247. package/dist/testYourself/server/index.d.ts +1029 -0
  248. package/dist/testYourself/server/index.js +825 -0
  249. package/dist/testYourself/server/index.js.map +1 -0
  250. package/dist/testYourself/server/index.mjs +816 -0
  251. package/dist/testYourself/server/index.mjs.map +1 -0
  252. package/dist/types-BTiaMsBz.d.mts +292 -0
  253. package/dist/types-DyG3ZV9V.d.mts +270 -0
  254. package/dist/types-DyG3ZV9V.d.ts +270 -0
  255. package/dist/types-ERmJyjx8.d.ts +292 -0
  256. package/dist/types-HorDyIRv.d.mts +303 -0
  257. package/dist/types-HorDyIRv.d.ts +303 -0
  258. package/dist/types-tQfupO6d.d.mts +70 -0
  259. package/dist/types-tQfupO6d.d.ts +70 -0
  260. package/dist/vocaloidBooth/index.d.mts +64 -0
  261. package/dist/vocaloidBooth/index.d.ts +64 -0
  262. package/dist/vocaloidBooth/index.js +376 -0
  263. package/dist/vocaloidBooth/index.js.map +1 -0
  264. package/dist/vocaloidBooth/index.mjs +362 -0
  265. package/dist/vocaloidBooth/index.mjs.map +1 -0
  266. package/dist/vocaloidBooth/server/index.d.mts +111 -0
  267. package/dist/vocaloidBooth/server/index.d.ts +111 -0
  268. package/dist/vocaloidBooth/server/index.js +247 -0
  269. package/dist/vocaloidBooth/server/index.js.map +1 -0
  270. package/dist/vocaloidBooth/server/index.mjs +237 -0
  271. package/dist/vocaloidBooth/server/index.mjs.map +1 -0
  272. package/dist/vocaloidBooth/web/index.d.mts +3 -0
  273. package/dist/vocaloidBooth/web/index.d.ts +3 -0
  274. package/dist/vocaloidBooth/web/index.js +376 -0
  275. package/dist/vocaloidBooth/web/index.js.map +1 -0
  276. package/dist/vocaloidBooth/web/index.mjs +362 -0
  277. package/dist/vocaloidBooth/web/index.mjs.map +1 -0
  278. package/package.json +11 -1
@@ -0,0 +1,1027 @@
1
+ import React2, { useState, useCallback, useEffect } from 'react';
2
+ import { clsx } from 'clsx';
3
+ import { Upload, CheckCircle2, X, Loader2, AlertTriangle, Search, Save, Settings, ChevronUp, ChevronDown, Plus, GripVertical, Trash2, List, Database, BarChart3, FileText, Music, Film } from 'lucide-react';
4
+ import JSZip from 'jszip';
5
+ import { relations } from 'drizzle-orm';
6
+ import { pgTable, timestamp, varchar, json, integer, uuid, boolean, text, index } from 'drizzle-orm/pg-core';
7
+
8
+ // src/mmd/admin/components/MmdAdminPanel.tsx
9
+
10
+ // src/mmd/admin/types.ts
11
+ var MMD_RESOURCE_TYPE_CONFIGS = {
12
+ model: {
13
+ moduleId: "mmd-models",
14
+ acceptedTypes: [".zip"],
15
+ maxFileSize: 150,
16
+ description: "MMD\u6A21\u578B\u538B\u7F29\u5305 (ZIP)",
17
+ hint: "\u8BF7\u4E0A\u4F20\u5305\u542B PMX/\u8D34\u56FE\u7B49\u5B8C\u6574\u76EE\u5F55\u7684 ZIP \u538B\u7F29\u5305\uFF0C\u4FDD\u6301\u539F\u59CB\u6587\u4EF6\u7ED3\u6784"
18
+ },
19
+ motion: {
20
+ moduleId: "mmd-motions",
21
+ acceptedTypes: [".vmd"],
22
+ maxFileSize: 20,
23
+ description: "MMD\u52A8\u4F5C\u6587\u4EF6"
24
+ },
25
+ camera: {
26
+ moduleId: "mmd-cameras",
27
+ acceptedTypes: [".vmd"],
28
+ maxFileSize: 10,
29
+ description: "MMD\u76F8\u673A\u52A8\u753B\u6587\u4EF6"
30
+ },
31
+ audio: {
32
+ moduleId: "mmd-audios",
33
+ acceptedTypes: [".mp3", ".wav", ".ogg", ".m4a"],
34
+ maxFileSize: 20,
35
+ description: "\u97F3\u9891\u6587\u4EF6"
36
+ },
37
+ stage: {
38
+ moduleId: "mmd-stages",
39
+ acceptedTypes: [".zip"],
40
+ maxFileSize: 200,
41
+ description: "\u821E\u53F0/\u573A\u666F\u538B\u7F29\u5305 (ZIP)",
42
+ hint: "\u9700\u5C06\u821E\u53F0\u6A21\u578B\u4E0E\u4F9D\u8D56\u8D34\u56FE\u4E00\u8D77\u6253\u5305 ZIP\uFF0C\u5E76\u4FDD\u6301\u76EE\u5F55\u7ED3\u6784"
43
+ },
44
+ thumbnail: {
45
+ moduleId: "mmd-thumbnails",
46
+ acceptedTypes: [".jpg", ".jpeg", ".png", ".webp"],
47
+ maxFileSize: 5,
48
+ description: "\u7F29\u7565\u56FE"
49
+ }
50
+ };
51
+
52
+ // src/mmd/admin/components/MmdResourceSelector.tsx
53
+ var MmdResourceSelector = ({
54
+ resourceType,
55
+ fileService,
56
+ userId,
57
+ value,
58
+ onChange,
59
+ required = false
60
+ }) => {
61
+ const [selectedFileId, setSelectedFileId] = useState(value);
62
+ const [selectedFile, setSelectedFile] = useState(null);
63
+ const [files, setFiles] = useState([]);
64
+ const [loading, setLoading] = useState(false);
65
+ const [uploading, setUploading] = useState(false);
66
+ const [searchTerm, setSearchTerm] = useState("");
67
+ const [showUploader, setShowUploader] = useState(false);
68
+ const [zipValidationError, setZipValidationError] = useState(null);
69
+ const config = MMD_RESOURCE_TYPE_CONFIGS[resourceType];
70
+ if (!config) {
71
+ return /* @__PURE__ */ React2.createElement("div", { className: "p-4 bg-red-50 text-red-600 rounded-lg" }, "\u672A\u627E\u5230\u8D44\u6E90\u7C7B\u578B\u914D\u7F6E\uFF1A", resourceType);
72
+ }
73
+ const getFileIcon = () => {
74
+ switch (resourceType) {
75
+ case "model":
76
+ case "stage":
77
+ return /* @__PURE__ */ React2.createElement(Film, { className: "w-5 h-5" });
78
+ case "motion":
79
+ case "camera":
80
+ return /* @__PURE__ */ React2.createElement(FileText, { className: "w-5 h-5" });
81
+ case "audio":
82
+ return /* @__PURE__ */ React2.createElement(Music, { className: "w-5 h-5" });
83
+ default:
84
+ return /* @__PURE__ */ React2.createElement(FileText, { className: "w-5 h-5" });
85
+ }
86
+ };
87
+ const loadFiles = useCallback(async () => {
88
+ if (!fileService || !config) return;
89
+ setLoading(true);
90
+ try {
91
+ const result = await fileService.queryFiles({
92
+ moduleId: config.moduleId,
93
+ pageSize: 50,
94
+ page: 1,
95
+ sortBy: "uploadTime",
96
+ sortOrder: "desc"
97
+ });
98
+ setFiles(result.items || []);
99
+ } catch (error) {
100
+ console.error("\u52A0\u8F7D\u6587\u4EF6\u5217\u8868\u5931\u8D25:", error);
101
+ } finally {
102
+ setLoading(false);
103
+ }
104
+ }, [fileService, config]);
105
+ useEffect(() => {
106
+ loadFiles();
107
+ }, [loadFiles]);
108
+ useEffect(() => {
109
+ if (selectedFileId && fileService) {
110
+ fileService.getFileMetadata(selectedFileId).then((file) => setSelectedFile(file)).catch((error) => console.error("\u52A0\u8F7D\u6587\u4EF6\u4FE1\u606F\u5931\u8D25:", error));
111
+ } else {
112
+ setSelectedFile(null);
113
+ }
114
+ }, [selectedFileId, fileService]);
115
+ const handleFileSelect = (file) => {
116
+ setSelectedFileId(file.id);
117
+ setSelectedFile(file);
118
+ fileService.getFileUrl(file.id).then((url) => onChange(file.id, url)).catch((error) => console.error("\u83B7\u53D6\u6587\u4EF6URL\u5931\u8D25:", error));
119
+ };
120
+ const validateZipContents = async (buffer, type) => {
121
+ const zip = await JSZip.loadAsync(buffer);
122
+ const entries = Object.keys(zip.files);
123
+ if (!entries.length) {
124
+ throw new Error("\u538B\u7F29\u5305\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5\u6587\u4EF6\u5185\u5BB9");
125
+ }
126
+ const hasModel = entries.some((name) => /\.[pP][mM][xX]$/.test(name)) || type === "stage" && entries.some((name) => /\.[pP][mM][dD]$/.test(name));
127
+ const hasAssets = entries.some(
128
+ (name) => /\.(png|jpg|jpeg|bmp|tga|dds|spa|sph)$/i.test(name)
129
+ );
130
+ if (!hasModel) {
131
+ throw new Error(type === "stage" ? "\u538B\u7F29\u5305\u4E2D\u672A\u627E\u5230 PMX/PMD \u821E\u53F0\u6A21\u578B\u6587\u4EF6" : "\u538B\u7F29\u5305\u4E2D\u672A\u627E\u5230 PMX \u6A21\u578B\u6587\u4EF6");
132
+ }
133
+ if (!hasAssets) {
134
+ throw new Error("\u538B\u7F29\u5305\u4E2D\u672A\u53D1\u73B0\u8D34\u56FE\u6587\u4EF6\uFF0C\u8BF7\u786E\u8BA4\u662F\u5426\u5305\u542B texture \u76EE\u5F55");
135
+ }
136
+ };
137
+ const handleFileUpload = async (file) => {
138
+ if (!fileService) return;
139
+ setUploading(true);
140
+ setZipValidationError(null);
141
+ try {
142
+ if (resourceType === "model" || resourceType === "stage") {
143
+ const arrayBuffer = await file.arrayBuffer();
144
+ await validateZipContents(arrayBuffer, resourceType);
145
+ }
146
+ const fileMetadata = await fileService.uploadFile({
147
+ file,
148
+ moduleId: config.moduleId,
149
+ businessId: userId,
150
+ permission: "public"
151
+ });
152
+ await loadFiles();
153
+ handleFileSelect(fileMetadata);
154
+ setShowUploader(false);
155
+ } catch (error) {
156
+ console.error("\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25:", error);
157
+ const message = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
158
+ setZipValidationError(message);
159
+ alert("\u4E0A\u4F20\u5931\u8D25: " + message);
160
+ } finally {
161
+ setUploading(false);
162
+ }
163
+ };
164
+ const filteredFiles = files.filter(
165
+ (file) => file.originalName.toLowerCase().includes(searchTerm.toLowerCase())
166
+ );
167
+ return /* @__PURE__ */ React2.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2.createElement("label", { className: "flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300" }, getFileIcon(), config.description, required && /* @__PURE__ */ React2.createElement("span", { className: "text-red-500" }, "*")), /* @__PURE__ */ React2.createElement(
168
+ "button",
169
+ {
170
+ onClick: () => setShowUploader(!showUploader),
171
+ className: "px-3 py-1 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2"
172
+ },
173
+ /* @__PURE__ */ React2.createElement(Upload, { className: "w-4 h-4" }),
174
+ "\u4E0A\u4F20\u65B0\u6587\u4EF6"
175
+ )), selectedFile && /* @__PURE__ */ React2.createElement("div", { className: "p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg flex items-center justify-between" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React2.createElement(CheckCircle2, { className: "w-5 h-5 text-green-600 dark:text-green-400" }), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "text-sm font-medium text-gray-900 dark:text-white" }, selectedFile.originalName), /* @__PURE__ */ React2.createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400" }, (selectedFile.size / 1024 / 1024).toFixed(2), " MB"))), /* @__PURE__ */ React2.createElement(
176
+ "button",
177
+ {
178
+ onClick: () => {
179
+ setSelectedFileId(void 0);
180
+ setSelectedFile(null);
181
+ onChange("", "");
182
+ },
183
+ className: "p-1 hover:bg-white/50 dark:hover:bg-black/20 rounded transition-colors"
184
+ },
185
+ /* @__PURE__ */ React2.createElement(X, { className: "w-4 h-4" })
186
+ )), showUploader && /* @__PURE__ */ React2.createElement("div", { className: "p-4 bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg" }, /* @__PURE__ */ React2.createElement("div", { className: "text-sm text-gray-600 dark:text-gray-400 mb-3 space-y-1" }, /* @__PURE__ */ React2.createElement("p", null, "\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B: ", config.acceptedTypes.join(", ")), /* @__PURE__ */ React2.createElement("p", null, "\u6700\u5927\u6587\u4EF6\u5927\u5C0F: ", config.maxFileSize, "MB"), config.hint && /* @__PURE__ */ React2.createElement("p", { className: "text-xs text-amber-600 dark:text-amber-400" }, config.hint)), /* @__PURE__ */ React2.createElement(
187
+ "input",
188
+ {
189
+ type: "file",
190
+ accept: config.acceptedTypes.join(","),
191
+ onChange: (e) => {
192
+ const file = e.target.files?.[0];
193
+ if (file) {
194
+ if (file.size > config.maxFileSize * 1024 * 1024) {
195
+ alert("\u6587\u4EF6\u5927\u5C0F\u8D85\u8FC7\u9650\u5236\uFF08\u6700\u5927 " + config.maxFileSize + "MB\uFF09");
196
+ return;
197
+ }
198
+ handleFileUpload(file);
199
+ }
200
+ },
201
+ disabled: uploading,
202
+ className: "w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 disabled:opacity-50"
203
+ }
204
+ ), uploading && /* @__PURE__ */ React2.createElement("div", { className: "mt-3 flex items-center gap-2 text-sm text-blue-600" }, /* @__PURE__ */ React2.createElement(Loader2, { className: "w-4 h-4 animate-spin" }), "\u4E0A\u4F20\u4E2D..."), zipValidationError && /* @__PURE__ */ React2.createElement("div", { className: "mt-3 flex items-center gap-2 text-sm text-amber-600 dark:text-amber-400" }, /* @__PURE__ */ React2.createElement(AlertTriangle, { className: "w-4 h-4" }), zipValidationError)), /* @__PURE__ */ React2.createElement("div", { className: "relative" }, /* @__PURE__ */ React2.createElement(Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" }), /* @__PURE__ */ React2.createElement(
205
+ "input",
206
+ {
207
+ type: "text",
208
+ placeholder: "\u641C\u7D22\u6587\u4EF6...",
209
+ value: searchTerm,
210
+ onChange: (e) => setSearchTerm(e.target.value),
211
+ className: "w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
212
+ }
213
+ )), /* @__PURE__ */ React2.createElement("div", { className: "max-h-96 overflow-y-auto border border-gray-200 dark:border-gray-700 rounded-lg divide-y divide-gray-200 dark:divide-gray-700" }, loading ? /* @__PURE__ */ React2.createElement("div", { className: "p-8 text-center text-gray-500" }, /* @__PURE__ */ React2.createElement(Loader2, { className: "w-6 h-6 animate-spin mx-auto mb-2" }), "\u52A0\u8F7D\u4E2D...") : filteredFiles.length === 0 ? /* @__PURE__ */ React2.createElement("div", { className: "p-8 text-center text-gray-500" }, searchTerm ? "\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6587\u4EF6" : "\u6682\u65E0\u6587\u4EF6\uFF0C\u8BF7\u4E0A\u4F20") : filteredFiles.map((file) => /* @__PURE__ */ React2.createElement(
214
+ "button",
215
+ {
216
+ key: file.id,
217
+ onClick: () => handleFileSelect(file),
218
+ className: clsx("w-full p-3 text-left hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors", selectedFileId === file.id ? "bg-blue-50 dark:bg-blue-900/20" : "")
219
+ },
220
+ /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React2.createElement("div", { className: "text-sm font-medium text-gray-900 dark:text-white truncate" }, file.originalName), /* @__PURE__ */ React2.createElement("div", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5" }, (file.size / 1024 / 1024).toFixed(2), " MB \u2022", " ", new Date(file.uploadTime).toLocaleDateString())), selectedFileId === file.id && /* @__PURE__ */ React2.createElement(CheckCircle2, { className: "w-5 h-5 text-blue-600 dark:text-blue-400 flex-shrink-0 ml-2" }))
221
+ ))), required && !selectedFileId && /* @__PURE__ */ React2.createElement("div", { className: "text-sm text-red-600 dark:text-red-400" }, "\u8BF7\u9009\u62E9\u4E00\u4E2A", config.description));
222
+ };
223
+
224
+ // src/mmd/admin/components/MmdPlaylistEditor.tsx
225
+ var MmdPlaylistEditor = ({
226
+ playlistId,
227
+ fileService,
228
+ userId,
229
+ onSave,
230
+ onCancel
231
+ }) => {
232
+ const [playlistName, setPlaylistName] = useState("");
233
+ const [playlistDescription, setPlaylistDescription] = useState("");
234
+ const [loop, setLoop] = useState(false);
235
+ const [autoPlay, setAutoPlay] = useState(false);
236
+ const [preloadStrategy, setPreloadStrategy] = useState("none");
237
+ const [nodes, setNodes] = useState([]);
238
+ const [expandedNodes, setExpandedNodes] = useState(/* @__PURE__ */ new Set([0]));
239
+ const [saving, setSaving] = useState(false);
240
+ const [showAdvanced, setShowAdvanced] = useState(false);
241
+ const addNode = () => {
242
+ const newNode = {
243
+ name: "\u8282\u70B9 " + (nodes.length + 1),
244
+ description: "",
245
+ loop: false,
246
+ sortOrder: nodes.length,
247
+ modelFileId: ""
248
+ };
249
+ setNodes([...nodes, newNode]);
250
+ setExpandedNodes(/* @__PURE__ */ new Set([...expandedNodes, nodes.length]));
251
+ };
252
+ const removeNode = (index2) => {
253
+ const newNodes = nodes.filter((_, i) => i !== index2);
254
+ newNodes.forEach((node, i) => {
255
+ node.sortOrder = i;
256
+ });
257
+ setNodes(newNodes);
258
+ const newExpanded = /* @__PURE__ */ new Set();
259
+ expandedNodes.forEach((i) => {
260
+ if (i < index2) newExpanded.add(i);
261
+ else if (i > index2) newExpanded.add(i - 1);
262
+ });
263
+ setExpandedNodes(newExpanded);
264
+ };
265
+ const moveNode = (index2, direction) => {
266
+ const newNodes = [...nodes];
267
+ const targetIndex = direction === "up" ? index2 - 1 : index2 + 1;
268
+ if (targetIndex < 0 || targetIndex >= newNodes.length) return;
269
+ if (!newNodes[index2] || !newNodes[targetIndex]) return;
270
+ [newNodes[index2], newNodes[targetIndex]] = [newNodes[targetIndex], newNodes[index2]];
271
+ newNodes.forEach((node, i) => {
272
+ node.sortOrder = i;
273
+ });
274
+ setNodes(newNodes);
275
+ const newExpanded = /* @__PURE__ */ new Set();
276
+ expandedNodes.forEach((i) => {
277
+ if (i === index2) newExpanded.add(targetIndex);
278
+ else if (i === targetIndex) newExpanded.add(index2);
279
+ else newExpanded.add(i);
280
+ });
281
+ setExpandedNodes(newExpanded);
282
+ };
283
+ const toggleNode = (index2) => {
284
+ const newExpanded = new Set(expandedNodes);
285
+ if (newExpanded.has(index2)) {
286
+ newExpanded.delete(index2);
287
+ } else {
288
+ newExpanded.add(index2);
289
+ }
290
+ setExpandedNodes(newExpanded);
291
+ };
292
+ const updateNode = (index2, updates) => {
293
+ const newNodes = [...nodes];
294
+ if (!newNodes[index2]) return;
295
+ newNodes[index2] = { ...newNodes[index2], ...updates };
296
+ setNodes(newNodes);
297
+ };
298
+ const validateForm = () => {
299
+ if (!playlistName.trim()) {
300
+ alert("\u8BF7\u8F93\u5165\u64AD\u653E\u5217\u8868\u540D\u79F0");
301
+ return false;
302
+ }
303
+ if (nodes.length === 0) {
304
+ alert("\u8BF7\u81F3\u5C11\u6DFB\u52A0\u4E00\u4E2A\u64AD\u653E\u8282\u70B9");
305
+ return false;
306
+ }
307
+ for (let i = 0; i < nodes.length; i++) {
308
+ const node = nodes[i];
309
+ if (!node || !node.name?.trim()) {
310
+ alert("\u8282\u70B9 " + (i + 1) + ": \u8BF7\u8F93\u5165\u8282\u70B9\u540D\u79F0");
311
+ return false;
312
+ }
313
+ if (!node.modelFileId) {
314
+ alert("\u8282\u70B9 " + (i + 1) + ": \u8BF7\u9009\u62E9\u6A21\u578B\u6587\u4EF6");
315
+ return false;
316
+ }
317
+ }
318
+ return true;
319
+ };
320
+ const handleSave = async () => {
321
+ if (!validateForm()) return;
322
+ setSaving(true);
323
+ try {
324
+ console.log("\u4FDD\u5B58\u64AD\u653E\u5217\u8868:", {
325
+ name: playlistName,
326
+ description: playlistDescription,
327
+ loop,
328
+ autoPlay,
329
+ preloadStrategy,
330
+ nodes
331
+ });
332
+ alert("\u4FDD\u5B58\u6210\u529F\uFF01");
333
+ onSave?.(null);
334
+ } catch (error) {
335
+ console.error("\u4FDD\u5B58\u5931\u8D25:", error);
336
+ alert("\u4FDD\u5B58\u5931\u8D25: " + (error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"));
337
+ } finally {
338
+ setSaving(false);
339
+ }
340
+ };
341
+ return /* @__PURE__ */ React2.createElement("div", { className: "max-w-6xl mx-auto p-6 bg-white dark:bg-gray-900 rounded-lg shadow-lg" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between mb-6" }, /* @__PURE__ */ React2.createElement("h2", { className: "text-2xl font-bold text-gray-900 dark:text-white" }, playlistId ? "\u7F16\u8F91\u64AD\u653E\u5217\u8868" : "\u521B\u5EFA\u64AD\u653E\u5217\u8868"), /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React2.createElement(
342
+ "button",
343
+ {
344
+ onClick: handleSave,
345
+ disabled: saving,
346
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 flex items-center gap-2"
347
+ },
348
+ /* @__PURE__ */ React2.createElement(Save, { className: "w-4 h-4" }),
349
+ saving ? "\u4FDD\u5B58\u4E2D..." : "\u4FDD\u5B58"
350
+ ), /* @__PURE__ */ React2.createElement(
351
+ "button",
352
+ {
353
+ onClick: onCancel,
354
+ className: "px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors flex items-center gap-2"
355
+ },
356
+ /* @__PURE__ */ React2.createElement(X, { className: "w-4 h-4" }),
357
+ "\u53D6\u6D88"
358
+ ))), /* @__PURE__ */ React2.createElement("div", { className: "space-y-4 mb-8 p-6 bg-gray-50 dark:bg-gray-800 rounded-lg" }, /* @__PURE__ */ React2.createElement("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-4" }, "\u57FA\u672C\u4FE1\u606F"), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u64AD\u653E\u5217\u8868\u540D\u79F0 ", /* @__PURE__ */ React2.createElement("span", { className: "text-red-500" }, "*")), /* @__PURE__ */ React2.createElement(
359
+ "input",
360
+ {
361
+ type: "text",
362
+ value: playlistName,
363
+ onChange: (e) => setPlaylistName(e.target.value),
364
+ placeholder: "\u8F93\u5165\u64AD\u653E\u5217\u8868\u540D\u79F0...",
365
+ className: "w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
366
+ }
367
+ )), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u63CF\u8FF0"), /* @__PURE__ */ React2.createElement(
368
+ "textarea",
369
+ {
370
+ value: playlistDescription,
371
+ onChange: (e) => setPlaylistDescription(e.target.value),
372
+ placeholder: "\u8F93\u5165\u63CF\u8FF0\u4FE1\u606F...",
373
+ rows: 3,
374
+ className: "w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
375
+ }
376
+ )), /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-6" }, /* @__PURE__ */ React2.createElement("label", { className: "flex items-center gap-2 cursor-pointer" }, /* @__PURE__ */ React2.createElement(
377
+ "input",
378
+ {
379
+ type: "checkbox",
380
+ checked: loop,
381
+ onChange: (e) => setLoop(e.target.checked),
382
+ className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
383
+ }
384
+ ), /* @__PURE__ */ React2.createElement("span", { className: "text-sm text-gray-700 dark:text-gray-300" }, "\u5217\u8868\u5FAA\u73AF")), /* @__PURE__ */ React2.createElement("label", { className: "flex items-center gap-2 cursor-pointer" }, /* @__PURE__ */ React2.createElement(
385
+ "input",
386
+ {
387
+ type: "checkbox",
388
+ checked: autoPlay,
389
+ onChange: (e) => setAutoPlay(e.target.checked),
390
+ className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
391
+ }
392
+ ), /* @__PURE__ */ React2.createElement("span", { className: "text-sm text-gray-700 dark:text-gray-300" }, "\u81EA\u52A8\u64AD\u653E"))), /* @__PURE__ */ React2.createElement(
393
+ "button",
394
+ {
395
+ onClick: () => setShowAdvanced(!showAdvanced),
396
+ className: "flex items-center gap-2 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300"
397
+ },
398
+ /* @__PURE__ */ React2.createElement(Settings, { className: "w-4 h-4" }),
399
+ "\u9AD8\u7EA7\u9009\u9879",
400
+ showAdvanced ? /* @__PURE__ */ React2.createElement(ChevronUp, { className: "w-4 h-4" }) : /* @__PURE__ */ React2.createElement(ChevronDown, { className: "w-4 h-4" })
401
+ ), showAdvanced && /* @__PURE__ */ React2.createElement("div", { className: "space-y-4 pt-4 border-t border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u9884\u52A0\u8F7D\u7B56\u7565"), /* @__PURE__ */ React2.createElement(
402
+ "select",
403
+ {
404
+ value: preloadStrategy,
405
+ onChange: (e) => setPreloadStrategy(e.target.value),
406
+ className: "w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
407
+ },
408
+ /* @__PURE__ */ React2.createElement("option", { value: "none" }, "\u4E0D\u9884\u52A0\u8F7D"),
409
+ /* @__PURE__ */ React2.createElement("option", { value: "next" }, "\u9884\u52A0\u8F7D\u4E0B\u4E00\u4E2A"),
410
+ /* @__PURE__ */ React2.createElement("option", { value: "all" }, "\u9884\u52A0\u8F7D\u5168\u90E8")
411
+ )))), /* @__PURE__ */ React2.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React2.createElement("h3", { className: "text-lg font-semibold text-gray-900 dark:text-white" }, "\u64AD\u653E\u8282\u70B9 (", nodes.length, ")"), /* @__PURE__ */ React2.createElement(
412
+ "button",
413
+ {
414
+ onClick: addNode,
415
+ className: "px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center gap-2"
416
+ },
417
+ /* @__PURE__ */ React2.createElement(Plus, { className: "w-4 h-4" }),
418
+ "\u6DFB\u52A0\u8282\u70B9"
419
+ )), nodes.length === 0 ? /* @__PURE__ */ React2.createElement("div", { className: "p-8 text-center text-gray-500 border-2 border-dashed border-gray-300 dark:border-gray-700 rounded-lg" }, '\u6682\u65E0\u8282\u70B9\uFF0C\u8BF7\u70B9\u51FB"\u6DFB\u52A0\u8282\u70B9"\u6309\u94AE\u5F00\u59CB') : nodes.map((node, index2) => /* @__PURE__ */ React2.createElement(
420
+ "div",
421
+ {
422
+ key: index2,
423
+ className: "border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden"
424
+ },
425
+ /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-3 p-4 bg-gray-50 dark:bg-gray-800" }, /* @__PURE__ */ React2.createElement(
426
+ "button",
427
+ {
428
+ className: "cursor-move text-gray-400 hover:text-gray-600 dark:hover:text-gray-300",
429
+ title: "\u62D6\u62FD\u6392\u5E8F"
430
+ },
431
+ /* @__PURE__ */ React2.createElement(GripVertical, { className: "w-5 h-5" })
432
+ ), /* @__PURE__ */ React2.createElement(
433
+ "button",
434
+ {
435
+ onClick: () => toggleNode(index2),
436
+ className: "flex-1 flex items-center justify-between text-left"
437
+ },
438
+ /* @__PURE__ */ React2.createElement("span", { className: "font-medium text-gray-900 dark:text-white" }, index2 + 1, ". ", node.name || "\u672A\u547D\u540D\u8282\u70B9"),
439
+ expandedNodes.has(index2) ? /* @__PURE__ */ React2.createElement(ChevronUp, { className: "w-5 h-5 text-gray-400" }) : /* @__PURE__ */ React2.createElement(ChevronDown, { className: "w-5 h-5 text-gray-400" })
440
+ ), /* @__PURE__ */ React2.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React2.createElement(
441
+ "button",
442
+ {
443
+ onClick: () => moveNode(index2, "up"),
444
+ disabled: index2 === 0,
445
+ className: "p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30",
446
+ title: "\u4E0A\u79FB"
447
+ },
448
+ /* @__PURE__ */ React2.createElement(ChevronUp, { className: "w-5 h-5" })
449
+ ), /* @__PURE__ */ React2.createElement(
450
+ "button",
451
+ {
452
+ onClick: () => moveNode(index2, "down"),
453
+ disabled: index2 === nodes.length - 1,
454
+ className: "p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30",
455
+ title: "\u4E0B\u79FB"
456
+ },
457
+ /* @__PURE__ */ React2.createElement(ChevronDown, { className: "w-5 h-5" })
458
+ ), /* @__PURE__ */ React2.createElement(
459
+ "button",
460
+ {
461
+ onClick: () => removeNode(index2),
462
+ className: "p-1 text-red-400 hover:text-red-600 dark:hover:text-red-300",
463
+ title: "\u5220\u9664"
464
+ },
465
+ /* @__PURE__ */ React2.createElement(Trash2, { className: "w-5 h-5" })
466
+ ))),
467
+ expandedNodes.has(index2) && /* @__PURE__ */ React2.createElement("div", { className: "p-6 space-y-6 bg-white dark:bg-gray-900" }, /* @__PURE__ */ React2.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u8282\u70B9\u540D\u79F0 ", /* @__PURE__ */ React2.createElement("span", { className: "text-red-500" }, "*")), /* @__PURE__ */ React2.createElement(
468
+ "input",
469
+ {
470
+ type: "text",
471
+ value: node.name,
472
+ onChange: (e) => updateNode(index2, { name: e.target.value }),
473
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
474
+ }
475
+ )), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u65F6\u957F\uFF08\u79D2\uFF09"), /* @__PURE__ */ React2.createElement(
476
+ "input",
477
+ {
478
+ type: "number",
479
+ value: node.duration || "",
480
+ onChange: (e) => updateNode(index2, { duration: parseInt(e.target.value) || void 0 }),
481
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
482
+ }
483
+ ))), /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2" }, "\u63CF\u8FF0"), /* @__PURE__ */ React2.createElement(
484
+ "textarea",
485
+ {
486
+ value: node.description || "",
487
+ onChange: (e) => updateNode(index2, { description: e.target.value }),
488
+ rows: 2,
489
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
490
+ }
491
+ )), /* @__PURE__ */ React2.createElement("label", { className: "flex items-center gap-2 cursor-pointer" }, /* @__PURE__ */ React2.createElement(
492
+ "input",
493
+ {
494
+ type: "checkbox",
495
+ checked: node.loop,
496
+ onChange: (e) => updateNode(index2, { loop: e.target.checked }),
497
+ className: "w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
498
+ }
499
+ ), /* @__PURE__ */ React2.createElement("span", { className: "text-sm text-gray-700 dark:text-gray-300" }, "\u5355\u66F2\u5FAA\u73AF")), /* @__PURE__ */ React2.createElement("div", { className: "grid grid-cols-1 gap-6 pt-4 border-t border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2.createElement(
500
+ MmdResourceSelector,
501
+ {
502
+ resourceType: "model",
503
+ fileService,
504
+ userId,
505
+ value: node.modelFileId,
506
+ onChange: (fileId) => updateNode(index2, { modelFileId: fileId }),
507
+ required: true
508
+ }
509
+ ), /* @__PURE__ */ React2.createElement(
510
+ MmdResourceSelector,
511
+ {
512
+ resourceType: "motion",
513
+ fileService,
514
+ userId,
515
+ value: node.motionFileId,
516
+ onChange: (fileId) => updateNode(index2, { motionFileId: fileId })
517
+ }
518
+ ), /* @__PURE__ */ React2.createElement(
519
+ MmdResourceSelector,
520
+ {
521
+ resourceType: "audio",
522
+ fileService,
523
+ userId,
524
+ value: node.audioFileId,
525
+ onChange: (fileId) => updateNode(index2, { audioFileId: fileId })
526
+ }
527
+ )))
528
+ ))));
529
+ };
530
+
531
+ // src/mmd/admin/components/MmdAdminPanel.tsx
532
+ var MmdAdminPanel = ({
533
+ fileService,
534
+ userId,
535
+ apiBaseUrl = "/api/mmd",
536
+ showAdvancedOptions = true,
537
+ className = ""
538
+ }) => {
539
+ const [activeTab, setActiveTab] = useState("playlists");
540
+ const [showEditor, setShowEditor] = useState(false);
541
+ const [editingPlaylistId, setEditingPlaylistId] = useState();
542
+ const tabs = [
543
+ { id: "playlists", label: "\u64AD\u653E\u5217\u8868", icon: List },
544
+ { id: "presets", label: "\u9884\u8BBE\u9879", icon: Database },
545
+ { id: "resources", label: "\u8D44\u6E90\u7BA1\u7406", icon: Settings },
546
+ ...showAdvancedOptions ? [{ id: "stats", label: "\u7EDF\u8BA1", icon: BarChart3 }] : []
547
+ ];
548
+ const handleCreatePlaylist = () => {
549
+ setEditingPlaylistId(void 0);
550
+ setShowEditor(true);
551
+ };
552
+ const handleCloseEditor = () => {
553
+ setShowEditor(false);
554
+ setEditingPlaylistId(void 0);
555
+ };
556
+ const handleSaveSuccess = (playlist) => {
557
+ console.log("\u4FDD\u5B58\u6210\u529F:", playlist);
558
+ handleCloseEditor();
559
+ };
560
+ if (showEditor) {
561
+ return /* @__PURE__ */ React2.createElement(
562
+ MmdPlaylistEditor,
563
+ {
564
+ playlistId: editingPlaylistId,
565
+ fileService,
566
+ userId,
567
+ onSave: handleSaveSuccess,
568
+ onCancel: handleCloseEditor
569
+ }
570
+ );
571
+ }
572
+ return /* @__PURE__ */ React2.createElement("div", { className: clsx("min-h-screen bg-gray-50 dark:bg-gray-900", className) }, /* @__PURE__ */ React2.createElement("div", { className: "bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700" }, /* @__PURE__ */ React2.createElement("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8" }, /* @__PURE__ */ React2.createElement("div", { className: "flex items-center justify-between h-16" }, /* @__PURE__ */ React2.createElement("h1", { className: "text-2xl font-bold text-gray-900 dark:text-white" }, "MMD \u540E\u53F0\u7BA1\u7406"), activeTab === "playlists" && /* @__PURE__ */ React2.createElement(
573
+ "button",
574
+ {
575
+ onClick: handleCreatePlaylist,
576
+ className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2"
577
+ },
578
+ /* @__PURE__ */ React2.createElement(Plus, { className: "w-4 h-4" }),
579
+ "\u521B\u5EFA\u64AD\u653E\u5217\u8868"
580
+ )), /* @__PURE__ */ React2.createElement("div", { className: "flex space-x-8" }, tabs.map((tab) => {
581
+ const Icon = tab.icon;
582
+ return /* @__PURE__ */ React2.createElement(
583
+ "button",
584
+ {
585
+ key: tab.id,
586
+ onClick: () => setActiveTab(tab.id),
587
+ className: clsx("flex items-center gap-2 px-1 py-4 border-b-2 font-medium text-sm transition-colors", activeTab === tab.id ? "border-blue-600 text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300")
588
+ },
589
+ /* @__PURE__ */ React2.createElement(Icon, { className: "w-4 h-4" }),
590
+ tab.label
591
+ );
592
+ })))), /* @__PURE__ */ React2.createElement("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8" }, activeTab === "playlists" && /* @__PURE__ */ React2.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React2.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2.createElement(List, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2.createElement("p", null, "\u64AD\u653E\u5217\u8868\u7BA1\u7406\u529F\u80FD\u5F00\u53D1\u4E2D..."), /* @__PURE__ */ React2.createElement("p", { className: "text-sm mt-2" }, '\u70B9\u51FB\u53F3\u4E0A\u89D2"\u521B\u5EFA\u64AD\u653E\u5217\u8868"\u6309\u94AE\u5F00\u59CB'))), activeTab === "presets" && /* @__PURE__ */ React2.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2.createElement(Database, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2.createElement("p", null, "\u9884\u8BBE\u9879\u7BA1\u7406\u529F\u80FD\u5F00\u53D1\u4E2D...")), activeTab === "resources" && /* @__PURE__ */ React2.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2.createElement(Settings, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2.createElement("p", null, "\u8D44\u6E90\u7BA1\u7406\u529F\u80FD\u5F00\u53D1\u4E2D...")), activeTab === "stats" && /* @__PURE__ */ React2.createElement("div", { className: "text-center py-12 text-gray-500" }, /* @__PURE__ */ React2.createElement(BarChart3, { className: "w-12 h-12 mx-auto mb-4 opacity-50" }), /* @__PURE__ */ React2.createElement("p", null, "\u7EDF\u8BA1\u529F\u80FD\u5F00\u53D1\u4E2D..."))));
593
+ };
594
+
595
+ // src/mmd/admin/utils.ts
596
+ function extractFileIdsFromPlaylist(playlist, nodes) {
597
+ const fileIds = /* @__PURE__ */ new Set();
598
+ if (playlist.thumbnailFileId) {
599
+ fileIds.add(playlist.thumbnailFileId);
600
+ }
601
+ for (const node of nodes) {
602
+ if (node.thumbnailFileId) fileIds.add(node.thumbnailFileId);
603
+ if (node.modelFileId) fileIds.add(node.modelFileId);
604
+ if (node.motionFileId) fileIds.add(node.motionFileId);
605
+ if (node.cameraFileId) fileIds.add(node.cameraFileId);
606
+ if (node.audioFileId) fileIds.add(node.audioFileId);
607
+ if (node.stageModelFileId) fileIds.add(node.stageModelFileId);
608
+ if (node.additionalMotionFileIds) {
609
+ node.additionalMotionFileIds.forEach((id) => fileIds.add(id));
610
+ }
611
+ }
612
+ return Array.from(fileIds);
613
+ }
614
+ function extractFileIdsFromResourceOptions(options) {
615
+ const fileIds = /* @__PURE__ */ new Set();
616
+ for (const option of options) {
617
+ fileIds.add(option.fileId);
618
+ if (option.thumbnailFileId) {
619
+ fileIds.add(option.thumbnailFileId);
620
+ }
621
+ }
622
+ return Array.from(fileIds);
623
+ }
624
+ function extractFileIdsFromPresetItem(item) {
625
+ const fileIds = /* @__PURE__ */ new Set();
626
+ if (item.thumbnailFileId) fileIds.add(item.thumbnailFileId);
627
+ if (item.modelFileId) fileIds.add(item.modelFileId);
628
+ if (item.motionFileId) fileIds.add(item.motionFileId);
629
+ if (item.cameraFileId) fileIds.add(item.cameraFileId);
630
+ if (item.audioFileId) fileIds.add(item.audioFileId);
631
+ if (item.stageModelFileId) fileIds.add(item.stageModelFileId);
632
+ if (item.additionalMotionFileIds) {
633
+ item.additionalMotionFileIds.forEach((id) => fileIds.add(id));
634
+ }
635
+ return Array.from(fileIds);
636
+ }
637
+ function convertPlaylistNodeToFrontend(node, fileUrls) {
638
+ return {
639
+ id: node.id,
640
+ playlistId: node.playlistId,
641
+ name: node.name,
642
+ description: node.description,
643
+ loop: node.loop,
644
+ duration: node.duration,
645
+ sortOrder: node.sortOrder,
646
+ config: node.config,
647
+ createdAt: node.createdAt,
648
+ updatedAt: node.updatedAt,
649
+ // URL 映射
650
+ thumbnailUrl: node.thumbnailFileId ? fileUrls[node.thumbnailFileId] : void 0,
651
+ modelUrl: fileUrls[node.modelFileId] || "",
652
+ motionUrl: node.motionFileId ? fileUrls[node.motionFileId] : void 0,
653
+ cameraUrl: node.cameraFileId ? fileUrls[node.cameraFileId] : void 0,
654
+ audioUrl: node.audioFileId ? fileUrls[node.audioFileId] : void 0,
655
+ stageModelUrl: node.stageModelFileId ? fileUrls[node.stageModelFileId] : void 0,
656
+ additionalMotionUrls: node.additionalMotionFileIds?.map((id) => fileUrls[id]).filter((url) => Boolean(url))
657
+ };
658
+ }
659
+ function convertPlaylistToFrontend(playlist, nodes, fileUrls) {
660
+ return {
661
+ id: playlist.id,
662
+ name: playlist.name,
663
+ description: playlist.description,
664
+ loop: playlist.loop,
665
+ preloadStrategy: playlist.preloadStrategy,
666
+ autoPlay: playlist.autoPlay,
667
+ status: playlist.status,
668
+ sortOrder: playlist.sortOrder,
669
+ config: playlist.config,
670
+ createdBy: playlist.createdBy,
671
+ createdAt: playlist.createdAt,
672
+ updatedAt: playlist.updatedAt,
673
+ deletedAt: playlist.deletedAt,
674
+ // URL 映射
675
+ thumbnailUrl: playlist.thumbnailFileId ? fileUrls[playlist.thumbnailFileId] : void 0,
676
+ // 转换节点
677
+ nodes: nodes.sort((a, b) => a.sortOrder - b.sortOrder).map((node) => convertPlaylistNodeToFrontend(node, fileUrls))
678
+ };
679
+ }
680
+ function convertResourceOptionToFrontend(option, fileUrls) {
681
+ return {
682
+ id: option.id,
683
+ name: option.name,
684
+ description: option.description,
685
+ resourceType: option.resourceType,
686
+ tags: option.tags,
687
+ sortOrder: option.sortOrder,
688
+ isActive: option.isActive,
689
+ createdBy: option.createdBy,
690
+ createdAt: option.createdAt,
691
+ updatedAt: option.updatedAt,
692
+ // URL 映射
693
+ fileUrl: fileUrls[option.fileId] || "",
694
+ thumbnailUrl: option.thumbnailFileId ? fileUrls[option.thumbnailFileId] : void 0
695
+ };
696
+ }
697
+ function convertPresetItemToFrontend(item, fileUrls) {
698
+ return {
699
+ id: item.id,
700
+ name: item.name,
701
+ description: item.description,
702
+ sortOrder: item.sortOrder,
703
+ isActive: item.isActive,
704
+ tags: item.tags,
705
+ createdBy: item.createdBy,
706
+ createdAt: item.createdAt,
707
+ updatedAt: item.updatedAt,
708
+ // URL 映射
709
+ thumbnailUrl: item.thumbnailFileId ? fileUrls[item.thumbnailFileId] : void 0,
710
+ modelUrl: fileUrls[item.modelFileId] || "",
711
+ motionUrl: item.motionFileId ? fileUrls[item.motionFileId] : void 0,
712
+ cameraUrl: item.cameraFileId ? fileUrls[item.cameraFileId] : void 0,
713
+ audioUrl: item.audioFileId ? fileUrls[item.audioFileId] : void 0,
714
+ stageModelUrl: item.stageModelFileId ? fileUrls[item.stageModelFileId] : void 0,
715
+ additionalMotionUrls: item.additionalMotionFileIds?.map((id) => fileUrls[id]).filter((url) => Boolean(url))
716
+ };
717
+ }
718
+ function convertNodeToMmdFormat(node) {
719
+ return {
720
+ id: node.id,
721
+ name: node.name,
722
+ loop: node.loop,
723
+ duration: node.duration,
724
+ thumbnail: node.thumbnailUrl,
725
+ resources: {
726
+ modelPath: node.modelUrl,
727
+ motionPath: node.motionUrl,
728
+ cameraPath: node.cameraUrl,
729
+ audioPath: node.audioUrl,
730
+ stageModelPath: node.stageModelUrl,
731
+ additionalMotions: node.additionalMotionUrls
732
+ }
733
+ };
734
+ }
735
+ function convertPlaylistToMmdConfig(playlist) {
736
+ return {
737
+ id: playlist.id,
738
+ name: playlist.name,
739
+ nodes: playlist.nodes.map(convertNodeToMmdFormat),
740
+ loop: playlist.loop,
741
+ preload: playlist.preloadStrategy,
742
+ autoPlay: playlist.autoPlay
743
+ };
744
+ }
745
+ function convertPresetItemToMmdResource(item) {
746
+ return {
747
+ id: item.id,
748
+ name: item.name,
749
+ thumbnail: item.thumbnailUrl,
750
+ description: item.description,
751
+ resources: {
752
+ modelPath: item.modelUrl,
753
+ motionPath: item.motionUrl,
754
+ cameraPath: item.cameraUrl,
755
+ audioPath: item.audioUrl,
756
+ stageModelPath: item.stageModelUrl,
757
+ additionalMotions: item.additionalMotionUrls
758
+ }
759
+ };
760
+ }
761
+ function convertResourceOptionsToMmdFormat(options) {
762
+ const grouped = {
763
+ models: [],
764
+ motions: [],
765
+ cameras: [],
766
+ audios: [],
767
+ stages: []
768
+ };
769
+ for (const option of options) {
770
+ const resourceOption = {
771
+ id: option.id,
772
+ name: option.name,
773
+ path: option.fileUrl,
774
+ thumbnail: option.thumbnailUrl
775
+ };
776
+ switch (option.resourceType) {
777
+ case "model":
778
+ grouped.models.push(resourceOption);
779
+ break;
780
+ case "motion":
781
+ grouped.motions.push(resourceOption);
782
+ break;
783
+ case "camera":
784
+ grouped.cameras.push(resourceOption);
785
+ break;
786
+ case "audio":
787
+ grouped.audios.push(resourceOption);
788
+ break;
789
+ case "stage":
790
+ grouped.stages.push(resourceOption);
791
+ break;
792
+ }
793
+ }
794
+ return grouped;
795
+ }
796
+ function validateFileUrls(requiredFileIds, fileUrls) {
797
+ const missingIds = requiredFileIds.filter((id) => !fileUrls[id]);
798
+ return {
799
+ valid: missingIds.length === 0,
800
+ missingIds
801
+ };
802
+ }
803
+ function generateMockFileUrls(fileIds) {
804
+ const urls = {};
805
+ for (const id of fileIds) {
806
+ urls[id] = "/mock/files/" + id;
807
+ }
808
+ return urls;
809
+ }
810
+ function mergeFileUrlMaps(...maps) {
811
+ return Object.assign({}, ...maps);
812
+ }
813
+ function extractPathsFromMmdResources(resources) {
814
+ const paths = [resources.modelPath];
815
+ if (resources.motionPath) paths.push(resources.motionPath);
816
+ if (resources.cameraPath) paths.push(resources.cameraPath);
817
+ if (resources.audioPath) paths.push(resources.audioPath);
818
+ if (resources.stageModelPath) {
819
+ if (Array.isArray(resources.stageModelPath)) {
820
+ paths.push(...resources.stageModelPath);
821
+ } else {
822
+ paths.push(resources.stageModelPath);
823
+ }
824
+ }
825
+ if (resources.additionalMotions) paths.push(...resources.additionalMotions);
826
+ return paths;
827
+ }
828
+ var mmdPlaylists = pgTable(
829
+ "mmd_playlists",
830
+ {
831
+ /** 主键ID */
832
+ id: uuid("id").primaryKey().defaultRandom(),
833
+ /** 播放列表名称 */
834
+ name: varchar("name", { length: 255 }).notNull(),
835
+ /** 播放列表描述 */
836
+ description: text("description"),
837
+ /** 是否启用列表循环 */
838
+ loop: boolean("loop").notNull().default(false),
839
+ /** 预加载策略: none, next, all */
840
+ preloadStrategy: varchar("preload_strategy", { length: 20 }).notNull().default("none"),
841
+ /** 是否自动播放 */
842
+ autoPlay: boolean("auto_play").notNull().default(false),
843
+ /** 播放列表缩略图文件ID (关联 file_metadata.id) */
844
+ thumbnailFileId: uuid("thumbnail_file_id"),
845
+ /** 播放列表状态: draft, published, archived */
846
+ status: varchar("status", { length: 20 }).notNull().default("draft"),
847
+ /** 显示顺序 */
848
+ sortOrder: integer("sort_order").notNull().default(0),
849
+ /** 额外配置(JSON格式,存储舞台配置等) */
850
+ config: json("config"),
851
+ /** 创建者ID */
852
+ createdBy: varchar("created_by", { length: 255 }).notNull(),
853
+ /** 创建时间 */
854
+ createdAt: timestamp("created_at").defaultNow().notNull(),
855
+ /** 更新时间 */
856
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
857
+ /** 删除时间(软删除) */
858
+ deletedAt: timestamp("deleted_at")
859
+ },
860
+ (table) => ({
861
+ /** 按状态查询的索引 */
862
+ statusIndex: index("mmd_playlists_status_idx").on(table.status),
863
+ /** 按创建者查询的索引 */
864
+ createdByIndex: index("mmd_playlists_created_by_idx").on(table.createdBy),
865
+ /** 按删除状态查询的索引 */
866
+ deletedAtIndex: index("mmd_playlists_deleted_at_idx").on(table.deletedAt),
867
+ /** 按排序查询的索引 */
868
+ sortOrderIndex: index("mmd_playlists_sort_order_idx").on(table.sortOrder)
869
+ })
870
+ );
871
+ var mmdPlaylistNodes = pgTable(
872
+ "mmd_playlist_nodes",
873
+ {
874
+ /** 主键ID */
875
+ id: uuid("id").primaryKey().defaultRandom(),
876
+ /** 所属播放列表ID */
877
+ playlistId: uuid("playlist_id").references(() => mmdPlaylists.id, { onDelete: "cascade" }).notNull(),
878
+ /** 节点名称 */
879
+ name: varchar("name", { length: 255 }).notNull(),
880
+ /** 节点描述 */
881
+ description: text("description"),
882
+ /** 是否启用节点循环 */
883
+ loop: boolean("loop").notNull().default(false),
884
+ /** 预计时长(秒) */
885
+ duration: integer("duration"),
886
+ /** 节点缩略图文件ID */
887
+ thumbnailFileId: uuid("thumbnail_file_id"),
888
+ /** 显示顺序 */
889
+ sortOrder: integer("sort_order").notNull().default(0),
890
+ /** 模型文件ID (关联 file_metadata.id) */
891
+ modelFileId: uuid("model_file_id").notNull(),
892
+ /** 动作文件ID */
893
+ motionFileId: uuid("motion_file_id"),
894
+ /** 相机动画文件ID */
895
+ cameraFileId: uuid("camera_file_id"),
896
+ /** 音频文件ID */
897
+ audioFileId: uuid("audio_file_id"),
898
+ /** 舞台模型文件ID */
899
+ stageModelFileId: uuid("stage_model_file_id"),
900
+ /** 附加动作文件ID列表(JSON数组) */
901
+ additionalMotionFileIds: json("additional_motion_file_ids").$type(),
902
+ /** 额外配置(JSON格式) */
903
+ config: json("config"),
904
+ /** 创建时间 */
905
+ createdAt: timestamp("created_at").defaultNow().notNull(),
906
+ /** 更新时间 */
907
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
908
+ },
909
+ (table) => ({
910
+ /** 按播放列表查询的索引 */
911
+ playlistIndex: index("mmd_playlist_nodes_playlist_idx").on(table.playlistId),
912
+ /** 按排序查询的索引 */
913
+ sortOrderIndex: index("mmd_playlist_nodes_sort_order_idx").on(table.sortOrder),
914
+ /** 按模型文件查询的索引 */
915
+ modelFileIndex: index("mmd_playlist_nodes_model_file_idx").on(table.modelFileId),
916
+ /** 组合索引:播放列表+排序 */
917
+ playlistSortIndex: index("mmd_playlist_nodes_playlist_sort_idx").on(
918
+ table.playlistId,
919
+ table.sortOrder
920
+ )
921
+ })
922
+ );
923
+ var mmdResourceOptions = pgTable(
924
+ "mmd_resource_options",
925
+ {
926
+ /** 主键ID */
927
+ id: uuid("id").primaryKey().defaultRandom(),
928
+ /** 资源名称 */
929
+ name: varchar("name", { length: 255 }).notNull(),
930
+ /** 资源描述 */
931
+ description: text("description"),
932
+ /** 资源类型: model, motion, camera, audio, stage */
933
+ resourceType: varchar("resource_type", { length: 20 }).notNull(),
934
+ /** 文件ID (关联 file_metadata.id) */
935
+ fileId: uuid("file_id").notNull(),
936
+ /** 缩略图文件ID */
937
+ thumbnailFileId: uuid("thumbnail_file_id"),
938
+ /** 资源标签(JSON数组,用于分类和筛选) */
939
+ tags: json("tags").$type(),
940
+ /** 显示顺序 */
941
+ sortOrder: integer("sort_order").notNull().default(0),
942
+ /** 是否启用 */
943
+ isActive: boolean("is_active").notNull().default(true),
944
+ /** 创建者ID */
945
+ createdBy: varchar("created_by", { length: 255 }).notNull(),
946
+ /** 创建时间 */
947
+ createdAt: timestamp("created_at").defaultNow().notNull(),
948
+ /** 更新时间 */
949
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
950
+ },
951
+ (table) => ({
952
+ /** 按资源类型查询的索引 */
953
+ resourceTypeIndex: index("mmd_resource_options_resource_type_idx").on(table.resourceType),
954
+ /** 按文件ID查询的索引 */
955
+ fileIdIndex: index("mmd_resource_options_file_id_idx").on(table.fileId),
956
+ /** 按活跃状态查询的索引 */
957
+ isActiveIndex: index("mmd_resource_options_is_active_idx").on(table.isActive),
958
+ /** 按创建者查询的索引 */
959
+ createdByIndex: index("mmd_resource_options_created_by_idx").on(table.createdBy),
960
+ /** 组合索引:资源类型+活跃状态+排序 */
961
+ typeActiveSortIndex: index("mmd_resource_options_type_active_sort_idx").on(
962
+ table.resourceType,
963
+ table.isActive,
964
+ table.sortOrder
965
+ )
966
+ })
967
+ );
968
+ var mmdPresetItems = pgTable(
969
+ "mmd_preset_items",
970
+ {
971
+ /** 主键ID */
972
+ id: uuid("id").primaryKey().defaultRandom(),
973
+ /** 预设名称 */
974
+ name: varchar("name", { length: 255 }).notNull(),
975
+ /** 预设描述 */
976
+ description: text("description"),
977
+ /** 缩略图文件ID */
978
+ thumbnailFileId: uuid("thumbnail_file_id"),
979
+ /** 模型文件ID */
980
+ modelFileId: uuid("model_file_id").notNull(),
981
+ /** 动作文件ID */
982
+ motionFileId: uuid("motion_file_id"),
983
+ /** 相机动画文件ID */
984
+ cameraFileId: uuid("camera_file_id"),
985
+ /** 音频文件ID */
986
+ audioFileId: uuid("audio_file_id"),
987
+ /** 舞台模型文件ID */
988
+ stageModelFileId: uuid("stage_model_file_id"),
989
+ /** 附加动作文件ID列表(JSON数组) */
990
+ additionalMotionFileIds: json("additional_motion_file_ids").$type(),
991
+ /** 显示顺序 */
992
+ sortOrder: integer("sort_order").notNull().default(0),
993
+ /** 是否启用 */
994
+ isActive: boolean("is_active").notNull().default(true),
995
+ /** 预设标签(JSON数组) */
996
+ tags: json("tags").$type(),
997
+ /** 创建者ID */
998
+ createdBy: varchar("created_by", { length: 255 }).notNull(),
999
+ /** 创建时间 */
1000
+ createdAt: timestamp("created_at").defaultNow().notNull(),
1001
+ /** 更新时间 */
1002
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
1003
+ },
1004
+ (table) => ({
1005
+ /** 按活跃状态查询的索引 */
1006
+ isActiveIndex: index("mmd_preset_items_is_active_idx").on(table.isActive),
1007
+ /** 按排序查询的索引 */
1008
+ sortOrderIndex: index("mmd_preset_items_sort_order_idx").on(table.sortOrder),
1009
+ /** 按创建者查询的索引 */
1010
+ createdByIndex: index("mmd_preset_items_created_by_idx").on(table.createdBy),
1011
+ /** 按模型文件查询的索引 */
1012
+ modelFileIndex: index("mmd_preset_items_model_file_idx").on(table.modelFileId)
1013
+ })
1014
+ );
1015
+ var mmdPlaylistsRelations = relations(mmdPlaylists, ({ many }) => ({
1016
+ nodes: many(mmdPlaylistNodes)
1017
+ }));
1018
+ var mmdPlaylistNodesRelations = relations(mmdPlaylistNodes, ({ one }) => ({
1019
+ playlist: one(mmdPlaylists, {
1020
+ fields: [mmdPlaylistNodes.playlistId],
1021
+ references: [mmdPlaylists.id]
1022
+ })
1023
+ }));
1024
+
1025
+ export { MMD_RESOURCE_TYPE_CONFIGS, MmdAdminPanel, MmdPlaylistEditor, MmdResourceSelector, convertNodeToMmdFormat, convertPlaylistNodeToFrontend, convertPlaylistToFrontend, convertPlaylistToMmdConfig, convertPresetItemToFrontend, convertPresetItemToMmdResource, convertResourceOptionToFrontend, convertResourceOptionsToMmdFormat, extractFileIdsFromPlaylist, extractFileIdsFromPresetItem, extractFileIdsFromResourceOptions, extractPathsFromMmdResources, generateMockFileUrls, mergeFileUrlMaps, mmdPlaylistNodes, mmdPlaylistNodesRelations, mmdPlaylists, mmdPlaylistsRelations, mmdPresetItems, mmdResourceOptions, validateFileUrls };
1026
+ //# sourceMappingURL=index.mjs.map
1027
+ //# sourceMappingURL=index.mjs.map