@chocodrop/mcp 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/bin/chocodrop-mcp.js +13 -0
- package/package.json +42 -0
- package/runtime/.chocodrop-mcp-runtime +1 -0
- package/runtime/asset-bridge.js +460 -0
- package/runtime/mcp-server.js +107 -0
- package/runtime/site/examples/basic/README.md +38 -0
- package/runtime/site/examples/basic/index.html +1730 -0
- package/runtime/site/examples/bookmarklet-v2.html +141 -0
- package/runtime/site/examples/bookmarklet.html +126 -0
- package/runtime/site/examples/lofi-room/index.html +2570 -0
- package/runtime/site/examples/lofi-room/sandbox-lite.html +124 -0
- package/runtime/site/examples/music-garden/index.html +2150 -0
- package/runtime/site/examples/pixel-ocean/index.html +1780 -0
- package/runtime/site/examples/react-three-fiber/AdvancedExample.jsx +338 -0
- package/runtime/site/examples/react-three-fiber/App.jsx +97 -0
- package/runtime/site/examples/react-three-fiber/README.md +294 -0
- package/runtime/site/examples/react-three-fiber/package.json +25 -0
- package/runtime/site/examples/sdk-test/README.md +70 -0
- package/runtime/site/examples/space/index.html +1975 -0
- package/runtime/site/examples/toy-city/index.html +2288 -0
- package/runtime/site/examples/wabi-sabi/index.html +2515 -0
- package/runtime/site/getting-started.html +84 -0
- package/runtime/site/index.html +1370 -0
- package/runtime/site/public/CommandUI.js +6341 -0
- package/runtime/site/public/LiveCommandClient.js +432 -0
- package/runtime/site/public/SceneManager.js +5493 -0
- package/runtime/site/public/bookmarklet.js +206 -0
- package/runtime/site/public/bootstrap.js +50 -0
- package/runtime/site/public/chocodrop-demo.umd.js +24020 -0
- package/runtime/site/public/chocodrop-demo.umd.min.js +24020 -0
- package/runtime/site/public/chocodrop.esm.js +2 -0
- package/runtime/site/public/html-sandbox/frame.js +865 -0
- package/runtime/site/public/icons/icon-192.png +0 -0
- package/runtime/site/public/icons/icon-512.png +0 -0
- package/runtime/site/public/icons/icon-maskable-512.png +0 -0
- package/runtime/site/public/immersive-launcher.js +175 -0
- package/runtime/site/public/immersive.html +165 -0
- package/runtime/site/public/index.html +1011 -0
- package/runtime/site/public/index.js +9 -0
- package/runtime/site/public/load-chocodrop.js +25 -0
- package/runtime/site/public/load-three.js +26 -0
- package/runtime/site/public/manifest.webmanifest +59 -0
- package/runtime/site/public/pwa-bootstrap.js +128 -0
- package/runtime/site/public/sample-card.svg +15 -0
- package/runtime/site/public/service-worker.js +117 -0
- package/runtime/site/public/translation-dictionary.js +257 -0
- package/runtime/site/public/xr/xr-ui.css +338 -0
- package/runtime/site/public/xr-viewer.html +263 -0
- package/runtime/site/src/client/local-bridge.js +83 -0
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ChocoDrop Server</title>
|
|
7
|
+
<link rel="manifest" href="/manifest.webmanifest">
|
|
8
|
+
<meta name="theme-color" content="#6f35bc">
|
|
9
|
+
<link rel="apple-touch-icon" href="/icons/icon-192.png">
|
|
10
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
11
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
12
|
+
<meta name="mobile-web-app-capable" content="yes">
|
|
13
|
+
<script type="importmap">
|
|
14
|
+
{
|
|
15
|
+
"imports": {
|
|
16
|
+
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
<script type="module" src="/pwa-bootstrap.js"></script>
|
|
21
|
+
<style>
|
|
22
|
+
:root {
|
|
23
|
+
--primary-color: #4f46e5;
|
|
24
|
+
--primary-hover: #4338ca;
|
|
25
|
+
--success-color: #10b981;
|
|
26
|
+
--warning-color: #f59e0b;
|
|
27
|
+
--danger-color: #ef4444;
|
|
28
|
+
--bg-primary: #fefefe;
|
|
29
|
+
--bg-secondary: #f8fafc;
|
|
30
|
+
--text-primary: #1e293b;
|
|
31
|
+
--text-secondary: #64748b;
|
|
32
|
+
--border-color: #e2e8f0;
|
|
33
|
+
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
34
|
+
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
35
|
+
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
|
36
|
+
--radius-sm: 8px;
|
|
37
|
+
--radius-md: 12px;
|
|
38
|
+
--radius-lg: 16px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
body {
|
|
42
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Inter', sans-serif;
|
|
43
|
+
max-width: 900px;
|
|
44
|
+
margin: 0 auto;
|
|
45
|
+
padding: 24px;
|
|
46
|
+
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
|
47
|
+
color: var(--text-primary);
|
|
48
|
+
line-height: 1.6;
|
|
49
|
+
min-height: 100vh;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.header {
|
|
53
|
+
text-align: center;
|
|
54
|
+
margin-bottom: 32px;
|
|
55
|
+
padding: 32px;
|
|
56
|
+
background: var(--bg-primary);
|
|
57
|
+
border-radius: var(--radius-lg);
|
|
58
|
+
box-shadow: var(--shadow-lg);
|
|
59
|
+
border: 1px solid var(--border-color);
|
|
60
|
+
backdrop-filter: blur(10px);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.offline-banner {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: center;
|
|
67
|
+
gap: 12px;
|
|
68
|
+
padding: 14px 20px;
|
|
69
|
+
margin-bottom: 24px;
|
|
70
|
+
border-radius: var(--radius-md);
|
|
71
|
+
background: linear-gradient(135deg, rgba(239, 68, 68, 0.12), rgba(127, 29, 29, 0.25));
|
|
72
|
+
color: var(--danger-color);
|
|
73
|
+
font-weight: 600;
|
|
74
|
+
box-shadow: var(--shadow-md);
|
|
75
|
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.offline-banner.hidden {
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.immersive-launch {
|
|
83
|
+
background: linear-gradient(135deg, rgba(111, 53, 188, 0.18), rgba(111, 53, 188, 0.05));
|
|
84
|
+
border-radius: var(--radius-lg);
|
|
85
|
+
padding: 24px;
|
|
86
|
+
margin-bottom: 32px;
|
|
87
|
+
border: 1px solid rgba(111, 53, 188, 0.25);
|
|
88
|
+
box-shadow: var(--shadow-md);
|
|
89
|
+
display: grid;
|
|
90
|
+
gap: 16px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.immersive-launch button {
|
|
94
|
+
background: linear-gradient(135deg, #6f35bc, #7c3aed);
|
|
95
|
+
color: white;
|
|
96
|
+
font-size: 1.1rem;
|
|
97
|
+
padding: 18px 28px;
|
|
98
|
+
border-radius: var(--radius-md);
|
|
99
|
+
border: none;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
font-weight: 700;
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
gap: 10px;
|
|
106
|
+
box-shadow: var(--shadow-lg);
|
|
107
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.immersive-launch button:hover {
|
|
111
|
+
transform: translateY(-2px);
|
|
112
|
+
box-shadow: 0 10px 25px rgba(111, 53, 188, 0.45);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.immersive-launch button:active {
|
|
116
|
+
transform: translateY(0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.immersive-launch p {
|
|
120
|
+
margin: 0;
|
|
121
|
+
color: var(--text-secondary);
|
|
122
|
+
font-size: 0.95rem;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.header h1 {
|
|
126
|
+
margin: 0 0 8px 0;
|
|
127
|
+
font-size: 2.25rem;
|
|
128
|
+
font-weight: 800;
|
|
129
|
+
background: linear-gradient(135deg, var(--primary-color), #7c3aed);
|
|
130
|
+
-webkit-background-clip: text;
|
|
131
|
+
-webkit-text-fill-color: transparent;
|
|
132
|
+
background-clip: text;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.header p {
|
|
136
|
+
margin: 8px 0;
|
|
137
|
+
color: var(--text-secondary);
|
|
138
|
+
font-size: 1.1rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.status {
|
|
142
|
+
display: grid;
|
|
143
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
144
|
+
gap: 16px;
|
|
145
|
+
margin-bottom: 32px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.status-item {
|
|
149
|
+
background: var(--bg-primary);
|
|
150
|
+
padding: 24px 20px;
|
|
151
|
+
border-radius: var(--radius-md);
|
|
152
|
+
text-align: center;
|
|
153
|
+
box-shadow: var(--shadow-md);
|
|
154
|
+
border: 1px solid var(--border-color);
|
|
155
|
+
transition: all 0.2s ease;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.status-item:hover {
|
|
159
|
+
transform: translateY(-2px);
|
|
160
|
+
box-shadow: var(--shadow-lg);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.status-value {
|
|
164
|
+
font-size: 2rem;
|
|
165
|
+
font-weight: 700;
|
|
166
|
+
color: var(--primary-color);
|
|
167
|
+
margin-bottom: 4px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.status-label {
|
|
171
|
+
font-size: 0.875rem;
|
|
172
|
+
color: var(--text-secondary);
|
|
173
|
+
font-weight: 500;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.mode-selector {
|
|
177
|
+
display: flex;
|
|
178
|
+
gap: 8px;
|
|
179
|
+
margin-bottom: 32px;
|
|
180
|
+
padding: 8px;
|
|
181
|
+
background: var(--bg-primary);
|
|
182
|
+
border-radius: var(--radius-lg);
|
|
183
|
+
box-shadow: var(--shadow-md);
|
|
184
|
+
border: 1px solid var(--border-color);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.mode-btn {
|
|
188
|
+
flex: 1;
|
|
189
|
+
padding: 16px 24px;
|
|
190
|
+
border: none;
|
|
191
|
+
background: transparent;
|
|
192
|
+
border-radius: var(--radius-md);
|
|
193
|
+
font-weight: 600;
|
|
194
|
+
font-size: 1rem;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
transition: all 0.3s ease;
|
|
197
|
+
color: var(--text-secondary);
|
|
198
|
+
position: relative;
|
|
199
|
+
overflow: hidden;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.mode-btn.active {
|
|
203
|
+
background: var(--primary-color);
|
|
204
|
+
color: white;
|
|
205
|
+
box-shadow: var(--shadow-md);
|
|
206
|
+
transform: translateY(-1px);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.mode-btn:not(.active):hover {
|
|
210
|
+
background: var(--bg-secondary);
|
|
211
|
+
color: var(--text-primary);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.mode-btn::before {
|
|
215
|
+
content: '';
|
|
216
|
+
position: absolute;
|
|
217
|
+
top: 0;
|
|
218
|
+
left: -100%;
|
|
219
|
+
width: 100%;
|
|
220
|
+
height: 100%;
|
|
221
|
+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
|
222
|
+
transition: left 0.5s;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.mode-btn:hover::before {
|
|
226
|
+
left: 100%;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.api-section {
|
|
230
|
+
background: var(--bg-primary);
|
|
231
|
+
padding: 32px;
|
|
232
|
+
border-radius: var(--radius-lg);
|
|
233
|
+
margin-bottom: 24px;
|
|
234
|
+
box-shadow: var(--shadow-md);
|
|
235
|
+
border: 1px solid var(--border-color);
|
|
236
|
+
transition: all 0.2s ease;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.api-section:hover {
|
|
240
|
+
box-shadow: var(--shadow-lg);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.section-title {
|
|
244
|
+
margin: 0 0 24px 0;
|
|
245
|
+
font-size: 1.5rem;
|
|
246
|
+
font-weight: 700;
|
|
247
|
+
color: var(--text-primary);
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
gap: 12px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.endpoint {
|
|
254
|
+
background: #f8f9fa;
|
|
255
|
+
padding: 10px;
|
|
256
|
+
border-radius: 4px;
|
|
257
|
+
margin: 10px 0;
|
|
258
|
+
font-family: monospace;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.method {
|
|
262
|
+
display: inline-block;
|
|
263
|
+
padding: 2px 8px;
|
|
264
|
+
border-radius: 4px;
|
|
265
|
+
font-weight: bold;
|
|
266
|
+
color: white;
|
|
267
|
+
font-size: 12px;
|
|
268
|
+
margin-right: 8px;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.get { background: #28a745; }
|
|
272
|
+
.post { background: #007bff; }
|
|
273
|
+
|
|
274
|
+
.mode-content {
|
|
275
|
+
display: none;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.mode-content.active {
|
|
279
|
+
display: block;
|
|
280
|
+
animation: fadeIn 0.3s ease;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
@keyframes fadeIn {
|
|
284
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
285
|
+
to { opacity: 1; transform: translateY(0); }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.form-container {
|
|
289
|
+
background: var(--bg-secondary);
|
|
290
|
+
padding: 24px;
|
|
291
|
+
border-radius: var(--radius-md);
|
|
292
|
+
margin-top: 24px;
|
|
293
|
+
border: 1px solid var(--border-color);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.form-group {
|
|
297
|
+
margin-bottom: 20px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
label {
|
|
301
|
+
display: block;
|
|
302
|
+
margin-bottom: 8px;
|
|
303
|
+
font-weight: 600;
|
|
304
|
+
color: var(--text-primary);
|
|
305
|
+
font-size: 0.95rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
input, textarea, select {
|
|
309
|
+
width: 100%;
|
|
310
|
+
padding: 16px;
|
|
311
|
+
border: 2px solid var(--border-color);
|
|
312
|
+
border-radius: var(--radius-md);
|
|
313
|
+
font-size: 1rem;
|
|
314
|
+
box-sizing: border-box;
|
|
315
|
+
background: var(--bg-primary);
|
|
316
|
+
color: var(--text-primary);
|
|
317
|
+
transition: all 0.3s ease;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
input:focus, textarea:focus, select:focus {
|
|
321
|
+
outline: none;
|
|
322
|
+
border-color: var(--primary-color);
|
|
323
|
+
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.primary-btn {
|
|
327
|
+
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
|
|
328
|
+
color: white;
|
|
329
|
+
padding: 16px 32px;
|
|
330
|
+
border: none;
|
|
331
|
+
border-radius: var(--radius-md);
|
|
332
|
+
cursor: pointer;
|
|
333
|
+
font-size: 1rem;
|
|
334
|
+
font-weight: 600;
|
|
335
|
+
transition: all 0.3s ease;
|
|
336
|
+
box-shadow: var(--shadow-md);
|
|
337
|
+
position: relative;
|
|
338
|
+
overflow: hidden;
|
|
339
|
+
width: 100%;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.primary-btn:hover {
|
|
343
|
+
transform: translateY(-2px);
|
|
344
|
+
box-shadow: var(--shadow-lg);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.primary-btn:active {
|
|
348
|
+
transform: translateY(0);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.primary-btn::before {
|
|
352
|
+
content: '';
|
|
353
|
+
position: absolute;
|
|
354
|
+
top: 0;
|
|
355
|
+
left: -100%;
|
|
356
|
+
width: 100%;
|
|
357
|
+
height: 100%;
|
|
358
|
+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
|
359
|
+
transition: left 0.5s;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.primary-btn:hover::before {
|
|
363
|
+
left: 100%;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.secondary-btn {
|
|
367
|
+
background: var(--bg-secondary);
|
|
368
|
+
color: var(--text-primary);
|
|
369
|
+
border: 2px solid var(--border-color);
|
|
370
|
+
padding: 14px 30px;
|
|
371
|
+
border-radius: var(--radius-md);
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
font-size: 1rem;
|
|
374
|
+
font-weight: 600;
|
|
375
|
+
transition: all 0.3s ease;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.secondary-btn:hover {
|
|
379
|
+
background: var(--primary-color);
|
|
380
|
+
color: white;
|
|
381
|
+
border-color: var(--primary-color);
|
|
382
|
+
transform: translateY(-1px);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.response {
|
|
386
|
+
background: var(--bg-primary);
|
|
387
|
+
border: 2px solid var(--border-color);
|
|
388
|
+
border-radius: var(--radius-md);
|
|
389
|
+
padding: 20px;
|
|
390
|
+
margin-top: 20px;
|
|
391
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
392
|
+
font-size: 0.875rem;
|
|
393
|
+
white-space: pre-wrap;
|
|
394
|
+
max-height: 400px;
|
|
395
|
+
overflow-y: auto;
|
|
396
|
+
box-shadow: var(--shadow-sm);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.success {
|
|
400
|
+
border-color: var(--success-color);
|
|
401
|
+
background: linear-gradient(135deg, #ecfdf5, #f0fdf4);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.error {
|
|
405
|
+
border-color: var(--danger-color);
|
|
406
|
+
background: linear-gradient(135deg, #fef2f2, #fef2f2);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.loading {
|
|
410
|
+
display: inline-block;
|
|
411
|
+
width: 20px;
|
|
412
|
+
height: 20px;
|
|
413
|
+
border: 3px solid rgba(79, 70, 229, 0.3);
|
|
414
|
+
border-radius: 50%;
|
|
415
|
+
border-top-color: var(--primary-color);
|
|
416
|
+
animation: spin 1s ease-in-out infinite;
|
|
417
|
+
margin-right: 8px;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
@keyframes spin {
|
|
421
|
+
to { transform: rotate(360deg); }
|
|
422
|
+
}
|
|
423
|
+
</style>
|
|
424
|
+
</head>
|
|
425
|
+
<body>
|
|
426
|
+
<div class="header">
|
|
427
|
+
<h1>🌉 ChocoDrop</h1>
|
|
428
|
+
<p>ちょこっと魔法で、思いを形に ✨</p>
|
|
429
|
+
<p><strong>Server Status:</strong> <span id="serverStatus">🟡 ちょこっと準備中...</span></p>
|
|
430
|
+
</div>
|
|
431
|
+
|
|
432
|
+
<div id="offlineBanner" class="offline-banner hidden">
|
|
433
|
+
<span>⚠️ オフラインモード</span>
|
|
434
|
+
<span>保存済みのモデル情報で最低限の操作が可能です。</span>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
<div class="immersive-launch">
|
|
438
|
+
<button id="launchImmersive">
|
|
439
|
+
🚀 ワンタップで没入ビューを開く
|
|
440
|
+
</button>
|
|
441
|
+
<p>Quest 3 や WebXR 対応ブラウザで起動すると、没入ビュー内の「XR開始」ボタンだけで体験を開始できます。</p>
|
|
442
|
+
</div>
|
|
443
|
+
|
|
444
|
+
<div class="status" id="statusCards">
|
|
445
|
+
<div class="status-item">
|
|
446
|
+
<div class="status-value" id="uptimeValue">--</div>
|
|
447
|
+
<div class="status-label">Uptime (s)</div>
|
|
448
|
+
</div>
|
|
449
|
+
<div class="status-item">
|
|
450
|
+
<div class="status-value" id="servicesValue">--</div>
|
|
451
|
+
<div class="status-label">Services</div>
|
|
452
|
+
</div>
|
|
453
|
+
<div class="status-item">
|
|
454
|
+
<div class="status-value" id="requestsValue">0</div>
|
|
455
|
+
<div class="status-label">Requests</div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<!-- Mode Selector -->
|
|
460
|
+
<div class="mode-selector">
|
|
461
|
+
<button class="mode-btn active" data-mode="generate">✨ ちょこっと魔法</button>
|
|
462
|
+
<button class="mode-btn" data-mode="modify">✏️ 変更</button>
|
|
463
|
+
<button class="mode-btn" data-mode="delete">🗑️ 削除</button>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<!-- Generate Mode -->
|
|
467
|
+
<div id="generate-mode" class="mode-content active">
|
|
468
|
+
<div class="api-section">
|
|
469
|
+
<h3 class="section-title">✨ ちょこっと魔法をかける</h3>
|
|
470
|
+
<p style="color: var(--text-secondary); margin-bottom: 24px;">「右上にドラゴンを」「美しい桜の森」など、自由に表現してください</p>
|
|
471
|
+
|
|
472
|
+
<div class="form-container">
|
|
473
|
+
<form id="generateForm">
|
|
474
|
+
<div class="form-group">
|
|
475
|
+
<label for="generatePrompt">どんな世界を思い描いていますか?</label>
|
|
476
|
+
<input type="text" id="generatePrompt" placeholder="「右上にドラゴンを」「美しい桜の森を中央に」など..." required>
|
|
477
|
+
</div>
|
|
478
|
+
|
|
479
|
+
<div class="form-group">
|
|
480
|
+
<label for="generateService">AI Model:</label>
|
|
481
|
+
<select id="generateService">
|
|
482
|
+
<option value="">Loading models...</option>
|
|
483
|
+
</select>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
<button type="submit" class="primary-btn">
|
|
487
|
+
<span class="btn-text">ちょこっと魔法をかける ✨</span>
|
|
488
|
+
</button>
|
|
489
|
+
</form>
|
|
490
|
+
|
|
491
|
+
<div id="generateResponse" class="response" style="display: none;"></div>
|
|
492
|
+
</div>
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
<!-- Modify Mode -->
|
|
497
|
+
<div id="modify-mode" class="mode-content">
|
|
498
|
+
<div class="api-section">
|
|
499
|
+
<h3 class="section-title">✏️ Modify Existing Objects</h3>
|
|
500
|
+
<p style="color: var(--text-secondary); margin-bottom: 24px;">Change properties of objects already in your scene.</p>
|
|
501
|
+
|
|
502
|
+
<div class="form-container">
|
|
503
|
+
<form id="modifyForm">
|
|
504
|
+
<div class="form-group">
|
|
505
|
+
<label for="modifyCommand">What do you want to change?</label>
|
|
506
|
+
<input type="text" id="modifyCommand" placeholder="例: change the dragon to blue color, make the unicorn bigger" required>
|
|
507
|
+
</div>
|
|
508
|
+
|
|
509
|
+
<button type="submit" class="primary-btn">
|
|
510
|
+
<span class="btn-text">Apply Changes</span>
|
|
511
|
+
</button>
|
|
512
|
+
</form>
|
|
513
|
+
|
|
514
|
+
<div id="modifyResponse" class="response" style="display: none;"></div>
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
</div>
|
|
518
|
+
|
|
519
|
+
<!-- Delete Mode -->
|
|
520
|
+
<div id="delete-mode" class="mode-content">
|
|
521
|
+
<div class="api-section">
|
|
522
|
+
<h3 class="section-title">🗑️ Delete Objects</h3>
|
|
523
|
+
<p style="color: var(--text-secondary); margin-bottom: 24px;">Remove objects from your 3D scene.</p>
|
|
524
|
+
|
|
525
|
+
<div class="form-container">
|
|
526
|
+
<form id="deleteForm">
|
|
527
|
+
<div class="form-group">
|
|
528
|
+
<label for="deleteCommand">What do you want to delete?</label>
|
|
529
|
+
<input type="text" id="deleteCommand" placeholder="例: delete all dragons, remove the unicorn, clear everything" required>
|
|
530
|
+
</div>
|
|
531
|
+
|
|
532
|
+
<div style="display: flex; gap: 12px;">
|
|
533
|
+
<button type="submit" class="primary-btn" style="flex: 1;">
|
|
534
|
+
<span class="btn-text">Delete Selected</span>
|
|
535
|
+
</button>
|
|
536
|
+
<button type="button" id="clearAll" class="secondary-btn" style="flex: 1;">
|
|
537
|
+
Clear All
|
|
538
|
+
</button>
|
|
539
|
+
</div>
|
|
540
|
+
</form>
|
|
541
|
+
|
|
542
|
+
<div id="deleteResponse" class="response" style="display: none;"></div>
|
|
543
|
+
</div>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
|
|
547
|
+
<script>
|
|
548
|
+
const chocoPWA = window.chocoPWA || null;
|
|
549
|
+
let cachedSession = null;
|
|
550
|
+
let cachedModels = null;
|
|
551
|
+
let requestCount = 0;
|
|
552
|
+
|
|
553
|
+
function logError(tag, details = {}) {
|
|
554
|
+
if (!chocoPWA || typeof chocoPWA.appendLog !== 'function') {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const entry = { tag, ...details };
|
|
558
|
+
chocoPWA.appendLog(entry).catch(error => {
|
|
559
|
+
console.warn('⚠️ OPFSログ書き込みに失敗しました', error);
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function persistSession(data) {
|
|
564
|
+
if (!chocoPWA || typeof chocoPWA.saveSession !== 'function') {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
cachedSession = { ...(cachedSession || {}), ...data };
|
|
568
|
+
chocoPWA.saveSession(cachedSession).catch(error => {
|
|
569
|
+
console.warn('⚠️ セッション保存に失敗しました', error);
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function updateOfflineBanner() {
|
|
574
|
+
const banner = document.getElementById('offlineBanner');
|
|
575
|
+
if (!banner) return;
|
|
576
|
+
if (navigator.onLine) {
|
|
577
|
+
banner.classList.add('hidden');
|
|
578
|
+
} else {
|
|
579
|
+
banner.classList.remove('hidden');
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function openImmersiveView(autoStart = false) {
|
|
584
|
+
const target = autoStart ? '/immersive.html#autoplay' : '/immersive.html';
|
|
585
|
+
window.location.href = target;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function applySessionToUI() {
|
|
589
|
+
if (!cachedSession) return;
|
|
590
|
+
const promptInput = document.getElementById('generatePrompt');
|
|
591
|
+
const imageSelect = document.getElementById('generateService');
|
|
592
|
+
if (cachedSession.prompt && promptInput) {
|
|
593
|
+
promptInput.value = cachedSession.prompt;
|
|
594
|
+
}
|
|
595
|
+
if (cachedSession.service && imageSelect) {
|
|
596
|
+
const option = Array.from(imageSelect.options || []).find(opt => opt.value === cachedSession.service);
|
|
597
|
+
if (option) {
|
|
598
|
+
option.selected = true;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function initOfflineCache() {
|
|
604
|
+
if (!chocoPWA) return;
|
|
605
|
+
try {
|
|
606
|
+
cachedSession = await chocoPWA.loadSession();
|
|
607
|
+
} catch (error) {
|
|
608
|
+
console.warn('⚠️ セッションの読み込みに失敗しました', error);
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
cachedModels = await chocoPWA.loadModels();
|
|
612
|
+
} catch (error) {
|
|
613
|
+
console.warn('⚠️ モデル情報の読み込みに失敗しました', error);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
window.addEventListener('online', () => {
|
|
618
|
+
updateOfflineBanner();
|
|
619
|
+
loadModels({ preferNetwork: true });
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
window.addEventListener('offline', () => {
|
|
623
|
+
updateOfflineBanner();
|
|
624
|
+
loadModels({ preferNetwork: false });
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// 自然な応答システム
|
|
628
|
+
function getResponseType(prompt) {
|
|
629
|
+
// ユーザーの表現スタイルを自然に感知
|
|
630
|
+
if (/ちょこっと|ちょこん|少し|軽く/.test(prompt) || prompt.length < 15) {
|
|
631
|
+
return 'casual';
|
|
632
|
+
}
|
|
633
|
+
if (/美しい|幻想|素敵|魔法|世界|綺麗/.test(prompt)) {
|
|
634
|
+
return 'magical';
|
|
635
|
+
}
|
|
636
|
+
return 'balanced'; // 80%がここに該当
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function getNaturalMessages(type) {
|
|
640
|
+
const messages = {
|
|
641
|
+
casual: {
|
|
642
|
+
loading: 'ちょこんと配置中です...',
|
|
643
|
+
success: 'ちょこっとドロップしました!✨',
|
|
644
|
+
button: 'ちょこんと配置中...',
|
|
645
|
+
error: 'ちょこっと失敗... もう一度お試しください'
|
|
646
|
+
},
|
|
647
|
+
magical: {
|
|
648
|
+
loading: 'あなたの想いを形にしています... ✨',
|
|
649
|
+
success: '素敵な世界が完成しました!🎭✨',
|
|
650
|
+
button: '魔法をかけています...',
|
|
651
|
+
error: 'うまくいかなかったようです。少し表現を変えてもう一度魔法をかけてみませんか?'
|
|
652
|
+
},
|
|
653
|
+
balanced: {
|
|
654
|
+
loading: 'ちょこっと魔法をかけています... ✨',
|
|
655
|
+
success: 'ちょこんと配置完了!素敵ですね!🌟',
|
|
656
|
+
button: '魔法をかけています...',
|
|
657
|
+
error: 'ちょこっと失敗しました。もう一度お試しください'
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
return messages[type];
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Load available models on page load
|
|
664
|
+
async function loadModels({ preferNetwork = true } = {}) {
|
|
665
|
+
const imageSelect = document.getElementById('generateService');
|
|
666
|
+
if (!imageSelect) return;
|
|
667
|
+
|
|
668
|
+
const renderFromPayload = (payload) => {
|
|
669
|
+
const models = payload?.models || payload;
|
|
670
|
+
const imageModels = models?.image;
|
|
671
|
+
if (!imageModels || !Array.isArray(imageModels.options)) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
imageSelect.innerHTML = '';
|
|
676
|
+
imageModels.options.forEach(model => {
|
|
677
|
+
const option = document.createElement('option');
|
|
678
|
+
option.value = model;
|
|
679
|
+
option.textContent = model;
|
|
680
|
+
if (model === imageModels.default) {
|
|
681
|
+
option.selected = true;
|
|
682
|
+
option.textContent += ' (Default)';
|
|
683
|
+
}
|
|
684
|
+
imageSelect.appendChild(option);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
console.log('✅ Models loaded:', models);
|
|
688
|
+
return true;
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const useCachedModels = () => {
|
|
692
|
+
if (!cachedModels) return false;
|
|
693
|
+
const applied = renderFromPayload(cachedModels);
|
|
694
|
+
if (applied) {
|
|
695
|
+
console.log('📦 Cached models applied');
|
|
696
|
+
return true;
|
|
697
|
+
}
|
|
698
|
+
return false;
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
if (!preferNetwork || !navigator.onLine) {
|
|
702
|
+
if (useCachedModels()) {
|
|
703
|
+
applySessionToUI();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (!navigator.onLine) {
|
|
707
|
+
imageSelect.innerHTML = '<option value="default">Default Model</option>';
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const response = await fetch('/api/models', { cache: 'no-store' });
|
|
714
|
+
const data = await response.json();
|
|
715
|
+
|
|
716
|
+
if (data.success && renderFromPayload(data)) {
|
|
717
|
+
cachedModels = { models: data.models };
|
|
718
|
+
if (chocoPWA && typeof chocoPWA.saveModels === 'function') {
|
|
719
|
+
chocoPWA.saveModels(data.models).catch(error => {
|
|
720
|
+
console.warn('⚠️ モデル情報の保存に失敗しました', error);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
console.error('❌ Failed to load models:', data.error);
|
|
725
|
+
logError('models-failed', { error: data.error });
|
|
726
|
+
if (!useCachedModels()) {
|
|
727
|
+
imageSelect.innerHTML = '<option value="default">Default Model</option>';
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
} catch (error) {
|
|
731
|
+
console.error('❌ Error loading models:', error);
|
|
732
|
+
logError('models-error', { message: error.message });
|
|
733
|
+
if (!useCachedModels()) {
|
|
734
|
+
imageSelect.innerHTML = '<option value="default">Default Model</option>';
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
applySessionToUI();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function initDashboard() {
|
|
742
|
+
await initOfflineCache();
|
|
743
|
+
updateOfflineBanner();
|
|
744
|
+
await loadModels({ preferNetwork: true });
|
|
745
|
+
applySessionToUI();
|
|
746
|
+
|
|
747
|
+
const immersiveBtn = document.getElementById('launchImmersive');
|
|
748
|
+
if (immersiveBtn) {
|
|
749
|
+
immersiveBtn.addEventListener('click', () => openImmersiveView(true));
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
checkHealth();
|
|
753
|
+
loadServices();
|
|
754
|
+
|
|
755
|
+
setInterval(() => {
|
|
756
|
+
checkHealth();
|
|
757
|
+
}, 5000);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
document.addEventListener('DOMContentLoaded', initDashboard);
|
|
761
|
+
|
|
762
|
+
// Check server health
|
|
763
|
+
async function checkHealth() {
|
|
764
|
+
try {
|
|
765
|
+
const response = await fetch('/health');
|
|
766
|
+
const data = await response.json();
|
|
767
|
+
|
|
768
|
+
if (response.ok) {
|
|
769
|
+
document.getElementById('serverStatus').innerHTML = '🟢 魔法準備完了!';
|
|
770
|
+
document.getElementById('uptimeValue').textContent = Math.floor(data.uptime);
|
|
771
|
+
} else {
|
|
772
|
+
document.getElementById('serverStatus').innerHTML = '🔴 魔法がうまくいきません...';
|
|
773
|
+
}
|
|
774
|
+
} catch (error) {
|
|
775
|
+
document.getElementById('serverStatus').innerHTML = '🔴 魔法の接続が切れています...';
|
|
776
|
+
logError('health-error', { message: error.message });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// Load services
|
|
781
|
+
async function loadServices() {
|
|
782
|
+
try {
|
|
783
|
+
const response = await fetch('/api/services');
|
|
784
|
+
const data = await response.json();
|
|
785
|
+
|
|
786
|
+
if (response.ok && data.services) {
|
|
787
|
+
document.getElementById('servicesValue').textContent = data.services.length;
|
|
788
|
+
}
|
|
789
|
+
} catch (error) {
|
|
790
|
+
console.error('Failed to load services:', error);
|
|
791
|
+
logError('services-error', { message: error.message });
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Mode switching
|
|
796
|
+
document.querySelectorAll('.mode-btn').forEach(btn => {
|
|
797
|
+
btn.addEventListener('click', () => {
|
|
798
|
+
// Remove active class from all buttons
|
|
799
|
+
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
|
|
800
|
+
document.querySelectorAll('.mode-content').forEach(c => c.classList.remove('active'));
|
|
801
|
+
|
|
802
|
+
// Add active class to clicked button
|
|
803
|
+
btn.classList.add('active');
|
|
804
|
+
|
|
805
|
+
// Show corresponding mode content
|
|
806
|
+
const mode = btn.dataset.mode;
|
|
807
|
+
document.getElementById(`${mode}-mode`).classList.add('active');
|
|
808
|
+
});
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// Generate form
|
|
812
|
+
document.getElementById('generateForm').addEventListener('submit', async (e) => {
|
|
813
|
+
e.preventDefault();
|
|
814
|
+
|
|
815
|
+
const prompt = document.getElementById('generatePrompt').value;
|
|
816
|
+
const service = document.getElementById('generateService').value;
|
|
817
|
+
const responseDiv = document.getElementById('generateResponse');
|
|
818
|
+
const btnText = e.target.querySelector('.btn-text');
|
|
819
|
+
let lastResult = null;
|
|
820
|
+
let success = false;
|
|
821
|
+
|
|
822
|
+
responseDiv.style.display = 'block';
|
|
823
|
+
responseDiv.className = 'response';
|
|
824
|
+
// 自然な応答システム
|
|
825
|
+
const responseType = getResponseType(prompt);
|
|
826
|
+
const messages = getNaturalMessages(responseType);
|
|
827
|
+
|
|
828
|
+
responseDiv.innerHTML = `<div class="loading"></div>${messages.loading}`;
|
|
829
|
+
btnText.innerHTML = `<div class="loading"></div>${messages.button}`;
|
|
830
|
+
|
|
831
|
+
try {
|
|
832
|
+
requestCount++;
|
|
833
|
+
document.getElementById('requestsValue').textContent = requestCount;
|
|
834
|
+
|
|
835
|
+
const response = await fetch('/api/generate', {
|
|
836
|
+
method: 'POST',
|
|
837
|
+
headers: {
|
|
838
|
+
'Content-Type': 'application/json'
|
|
839
|
+
},
|
|
840
|
+
body: JSON.stringify({
|
|
841
|
+
prompt,
|
|
842
|
+
service,
|
|
843
|
+
width: 512,
|
|
844
|
+
height: 512
|
|
845
|
+
})
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
const result = await response.json();
|
|
849
|
+
lastResult = result;
|
|
850
|
+
if (response.ok) {
|
|
851
|
+
responseDiv.className = 'response success';
|
|
852
|
+
responseDiv.textContent = messages.success;
|
|
853
|
+
success = true;
|
|
854
|
+
} else {
|
|
855
|
+
responseDiv.className = 'response error';
|
|
856
|
+
responseDiv.textContent = messages.error;
|
|
857
|
+
logError('generate-response-error', { prompt, response: result });
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
} catch (error) {
|
|
861
|
+
responseDiv.className = 'response error';
|
|
862
|
+
responseDiv.textContent = messages.error;
|
|
863
|
+
lastResult = { error: error.message };
|
|
864
|
+
logError('generate-error', { prompt, message: error.message });
|
|
865
|
+
} finally {
|
|
866
|
+
btnText.textContent = 'ちょこっと魔法をかける ✨';
|
|
867
|
+
persistSession({
|
|
868
|
+
prompt,
|
|
869
|
+
service,
|
|
870
|
+
lastStatus: success ? 'success' : 'error',
|
|
871
|
+
lastResponse: lastResult
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// Modify form
|
|
877
|
+
document.getElementById('modifyForm').addEventListener('submit', async (e) => {
|
|
878
|
+
e.preventDefault();
|
|
879
|
+
|
|
880
|
+
const command = document.getElementById('modifyCommand').value;
|
|
881
|
+
const responseDiv = document.getElementById('modifyResponse');
|
|
882
|
+
const btnText = e.target.querySelector('.btn-text');
|
|
883
|
+
|
|
884
|
+
responseDiv.style.display = 'block';
|
|
885
|
+
responseDiv.className = 'response';
|
|
886
|
+
responseDiv.innerHTML = '<div class="loading"></div>Applying changes...';
|
|
887
|
+
btnText.innerHTML = '<div class="loading"></div>Modifying...';
|
|
888
|
+
|
|
889
|
+
try {
|
|
890
|
+
requestCount++;
|
|
891
|
+
document.getElementById('requestsValue').textContent = requestCount;
|
|
892
|
+
|
|
893
|
+
const response = await fetch('/api/command', {
|
|
894
|
+
method: 'POST',
|
|
895
|
+
headers: {
|
|
896
|
+
'Content-Type': 'application/json'
|
|
897
|
+
},
|
|
898
|
+
body: JSON.stringify({ command })
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
const result = await response.json();
|
|
902
|
+
|
|
903
|
+
if (response.ok) {
|
|
904
|
+
responseDiv.className = 'response success';
|
|
905
|
+
responseDiv.textContent = JSON.stringify(result, null, 2);
|
|
906
|
+
} else {
|
|
907
|
+
responseDiv.className = 'response error';
|
|
908
|
+
responseDiv.textContent = JSON.stringify(result, null, 2);
|
|
909
|
+
logError('modify-response-error', { command, response: result });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
} catch (error) {
|
|
913
|
+
responseDiv.className = 'response error';
|
|
914
|
+
responseDiv.textContent = `Error: ${error.message}`;
|
|
915
|
+
logError('modify-error', { command, message: error.message });
|
|
916
|
+
} finally {
|
|
917
|
+
btnText.textContent = 'Apply Changes';
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
// Delete form
|
|
922
|
+
document.getElementById('deleteForm').addEventListener('submit', async (e) => {
|
|
923
|
+
e.preventDefault();
|
|
924
|
+
|
|
925
|
+
const command = document.getElementById('deleteCommand').value;
|
|
926
|
+
const responseDiv = document.getElementById('deleteResponse');
|
|
927
|
+
const btnText = e.target.querySelector('.btn-text');
|
|
928
|
+
|
|
929
|
+
responseDiv.style.display = 'block';
|
|
930
|
+
responseDiv.className = 'response';
|
|
931
|
+
responseDiv.innerHTML = '<div class="loading"></div>Deleting objects...';
|
|
932
|
+
btnText.innerHTML = '<div class="loading"></div>Deleting...';
|
|
933
|
+
|
|
934
|
+
try {
|
|
935
|
+
requestCount++;
|
|
936
|
+
document.getElementById('requestsValue').textContent = requestCount;
|
|
937
|
+
|
|
938
|
+
const response = await fetch('/api/command', {
|
|
939
|
+
method: 'POST',
|
|
940
|
+
headers: {
|
|
941
|
+
'Content-Type': 'application/json'
|
|
942
|
+
},
|
|
943
|
+
body: JSON.stringify({ command })
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
const result = await response.json();
|
|
947
|
+
|
|
948
|
+
if (response.ok) {
|
|
949
|
+
responseDiv.className = 'response success';
|
|
950
|
+
responseDiv.textContent = JSON.stringify(result, null, 2);
|
|
951
|
+
} else {
|
|
952
|
+
responseDiv.className = 'response error';
|
|
953
|
+
responseDiv.textContent = JSON.stringify(result, null, 2);
|
|
954
|
+
logError('delete-response-error', { command, response: result });
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
} catch (error) {
|
|
958
|
+
responseDiv.className = 'response error';
|
|
959
|
+
responseDiv.textContent = `Error: ${error.message}`;
|
|
960
|
+
logError('delete-error', { command, message: error.message });
|
|
961
|
+
} finally {
|
|
962
|
+
btnText.textContent = 'Delete Selected';
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
// Clear all button
|
|
967
|
+
document.getElementById('clearAll').addEventListener('click', async () => {
|
|
968
|
+
if (!confirm('Are you sure you want to clear all objects from the scene?')) {
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const responseDiv = document.getElementById('deleteResponse');
|
|
973
|
+
|
|
974
|
+
responseDiv.style.display = 'block';
|
|
975
|
+
responseDiv.className = 'response';
|
|
976
|
+
responseDiv.innerHTML = '<div class="loading"></div>Clearing all objects...';
|
|
977
|
+
|
|
978
|
+
try {
|
|
979
|
+
requestCount++;
|
|
980
|
+
document.getElementById('requestsValue').textContent = requestCount;
|
|
981
|
+
|
|
982
|
+
const response = await fetch('/api/command', {
|
|
983
|
+
method: 'POST',
|
|
984
|
+
headers: {
|
|
985
|
+
'Content-Type': 'application/json'
|
|
986
|
+
},
|
|
987
|
+
body: JSON.stringify({ command: '全てのオブジェクトを削除' })
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
const result = await response.json();
|
|
991
|
+
|
|
992
|
+
if (response.ok) {
|
|
993
|
+
responseDiv.className = 'response success';
|
|
994
|
+
responseDiv.textContent = 'All objects cleared successfully!';
|
|
995
|
+
} else {
|
|
996
|
+
responseDiv.className = 'response error';
|
|
997
|
+
responseDiv.textContent = JSON.stringify(result, null, 2);
|
|
998
|
+
logError('clear-response-error', { response: result });
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
responseDiv.className = 'response error';
|
|
1003
|
+
responseDiv.textContent = `Error: ${error.message}`;
|
|
1004
|
+
logError('clear-error', { message: error.message });
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
console.log('🌉 ChocoDrop Server dashboard loaded');
|
|
1009
|
+
</script>
|
|
1010
|
+
</body>
|
|
1011
|
+
</html>
|