@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,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChocoDrop - Client Components
|
|
3
|
+
* Browser-side components for 3D scene integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { ChocoDropClient, ChocoDroClient, LiveCommandClient } from './LiveCommandClient.js';
|
|
7
|
+
export { SceneManager } from './SceneManager.js';
|
|
8
|
+
export { CommandUI } from './CommandUI.js';
|
|
9
|
+
export { createChocoDrop, createChocoDro, createLiveCommand } from './bootstrap.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import ensureThree from './load-three.js';
|
|
2
|
+
|
|
3
|
+
const BUNDLE_VERSION = '20260916-local-tools';
|
|
4
|
+
const BUNDLE_URL = new URL(`./chocodrop-demo.umd.min.js?v=${BUNDLE_VERSION}`, import.meta.url).href;
|
|
5
|
+
|
|
6
|
+
async function ensureChocoDrop() {
|
|
7
|
+
await ensureThree;
|
|
8
|
+
|
|
9
|
+
if (!window.__chocodropUmdPromise || window.__chocodropUmdPromise.version !== BUNDLE_VERSION) {
|
|
10
|
+
window.__chocodropUmdPromise = new Promise((resolve, reject) => {
|
|
11
|
+
const script = document.createElement('script');
|
|
12
|
+
script.src = BUNDLE_URL;
|
|
13
|
+
script.onload = () => resolve(window.ChocoDrop);
|
|
14
|
+
script.onerror = reject;
|
|
15
|
+
document.head.appendChild(script);
|
|
16
|
+
});
|
|
17
|
+
window.__chocodropUmdPromise.version = BUNDLE_VERSION;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await window.__chocodropUmdPromise;
|
|
21
|
+
return window.ChocoDrop;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { ensureChocoDrop };
|
|
25
|
+
export default ensureChocoDrop;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const THREE_VERSION = '0.170.0';
|
|
2
|
+
|
|
3
|
+
if (!window.__chocodropThreePromise) {
|
|
4
|
+
window.__chocodropThreePromise = (async () => {
|
|
5
|
+
if (window.THREE && window.THREE.GLTFLoader && window.THREE.OrbitControls) {
|
|
6
|
+
return window.THREE;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const THREE_MODULE = await import(`https://cdn.jsdelivr.net/npm/three@${THREE_VERSION}/build/three.module.js`);
|
|
10
|
+
const [{ OrbitControls }, { GLTFLoader }] = await Promise.all([
|
|
11
|
+
import(`https://cdn.jsdelivr.net/npm/three@${THREE_VERSION}/examples/jsm/controls/OrbitControls.js`),
|
|
12
|
+
import(`https://cdn.jsdelivr.net/npm/three@${THREE_VERSION}/examples/jsm/loaders/GLTFLoader.js`)
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
// Create extensible THREE object with all exports
|
|
16
|
+
window.THREE = {
|
|
17
|
+
...THREE_MODULE,
|
|
18
|
+
OrbitControls,
|
|
19
|
+
GLTFLoader
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return window.THREE;
|
|
23
|
+
})();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default window.__chocodropThreePromise;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ChocoDrop Immersive Studio",
|
|
3
|
+
"short_name": "ChocoDrop",
|
|
4
|
+
"description": "自然言語で3Dシーンに魔法をかける ChocoDrop の没入体験を、ホーム画面からワンタップで起動します。",
|
|
5
|
+
"lang": "ja",
|
|
6
|
+
"start_url": "/immersive.html",
|
|
7
|
+
"scope": "/",
|
|
8
|
+
"display": "standalone",
|
|
9
|
+
"display_override": [
|
|
10
|
+
"fullscreen",
|
|
11
|
+
"standalone"
|
|
12
|
+
],
|
|
13
|
+
"background_color": "#0f172a",
|
|
14
|
+
"theme_color": "#6f35bc",
|
|
15
|
+
"orientation": "landscape",
|
|
16
|
+
"icons": [
|
|
17
|
+
{
|
|
18
|
+
"src": "/icons/icon-192.png",
|
|
19
|
+
"sizes": "192x192",
|
|
20
|
+
"type": "image/png"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"src": "/icons/icon-512.png",
|
|
24
|
+
"sizes": "512x512",
|
|
25
|
+
"type": "image/png"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"src": "/icons/icon-maskable-512.png",
|
|
29
|
+
"sizes": "512x512",
|
|
30
|
+
"type": "image/png",
|
|
31
|
+
"purpose": "maskable"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"categories": [
|
|
35
|
+
"productivity",
|
|
36
|
+
"xr"
|
|
37
|
+
],
|
|
38
|
+
"shortcuts": [
|
|
39
|
+
{
|
|
40
|
+
"name": "没入ビューを開く",
|
|
41
|
+
"short_name": "没入開始",
|
|
42
|
+
"description": "XRビューをすぐに立ち上げます",
|
|
43
|
+
"url": "/immersive.html#autoplay",
|
|
44
|
+
"icons": [
|
|
45
|
+
{
|
|
46
|
+
"src": "/icons/icon-192.png",
|
|
47
|
+
"sizes": "192x192",
|
|
48
|
+
"type": "image/png"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "コントロールパネル",
|
|
54
|
+
"short_name": "管理UI",
|
|
55
|
+
"description": "サーバーの状態を確認してAI生成を操作します",
|
|
56
|
+
"url": "/index.html"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const SW_PATH = '/service-worker.js';
|
|
2
|
+
const SW_SCOPE = '/';
|
|
3
|
+
const SESSION_FILE = 'session.json';
|
|
4
|
+
const MODELS_FILE = 'models.json';
|
|
5
|
+
const LOG_FILE = 'chocodrop-dev.log';
|
|
6
|
+
|
|
7
|
+
const supportsOPFS = Boolean(navigator.storage && navigator.storage.getDirectory);
|
|
8
|
+
|
|
9
|
+
const opfs = {
|
|
10
|
+
supported: supportsOPFS,
|
|
11
|
+
rootPromise: null,
|
|
12
|
+
async getDirectory() {
|
|
13
|
+
if (!this.supported) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (!this.rootPromise) {
|
|
17
|
+
this.rootPromise = (async () => {
|
|
18
|
+
try {
|
|
19
|
+
const root = await navigator.storage.getDirectory();
|
|
20
|
+
return root.getDirectoryHandle('chocodrop-cache', { create: true });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.warn('⚠️ OPFS初期化に失敗しました', error);
|
|
23
|
+
this.supported = false;
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
}
|
|
28
|
+
return this.rootPromise;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function writeFile(handle, text) {
|
|
33
|
+
const writable = await handle.createWritable();
|
|
34
|
+
await writable.write(text);
|
|
35
|
+
await writable.close();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function saveJSON(filename, data) {
|
|
39
|
+
const dir = await opfs.getDirectory();
|
|
40
|
+
if (!dir) return false;
|
|
41
|
+
const handle = await dir.getFileHandle(filename, { create: true });
|
|
42
|
+
await writeFile(handle, JSON.stringify(data, null, 2));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readJSON(filename, fallback = null) {
|
|
47
|
+
const dir = await opfs.getDirectory();
|
|
48
|
+
if (!dir) return fallback;
|
|
49
|
+
try {
|
|
50
|
+
const handle = await dir.getFileHandle(filename, { create: true });
|
|
51
|
+
const file = await handle.getFile();
|
|
52
|
+
const text = await file.text();
|
|
53
|
+
return text ? JSON.parse(text) : fallback;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.warn('⚠️ OPFS読み込みに失敗しました', error);
|
|
56
|
+
return fallback;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function appendLogLine(text) {
|
|
61
|
+
const dir = await opfs.getDirectory();
|
|
62
|
+
if (!dir) return false;
|
|
63
|
+
const handle = await dir.getFileHandle(LOG_FILE, { create: true });
|
|
64
|
+
const timestamp = new Date().toISOString();
|
|
65
|
+
const file = await handle.getFile();
|
|
66
|
+
const existing = await file.text();
|
|
67
|
+
const next = `${existing || ''}${timestamp} ${text}\n`;
|
|
68
|
+
await writeFile(handle, next);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function registerServiceWorker() {
|
|
73
|
+
if (!('serviceWorker' in navigator)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const registration = await navigator.serviceWorker.register(SW_PATH, { scope: SW_SCOPE });
|
|
78
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
79
|
+
console.info('🔄 Service Worker controller changed (更新適用)');
|
|
80
|
+
});
|
|
81
|
+
return registration;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('❌ Service Worker登録に失敗しました', error);
|
|
84
|
+
await appendLogLine(`[sw-error] ${error.message || error}`);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function ensureOpfsBootstrap() {
|
|
90
|
+
await opfs.getDirectory();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!window.chocoPWA) {
|
|
94
|
+
window.chocoPWA = {
|
|
95
|
+
supportsOPFS,
|
|
96
|
+
ready: ensureOpfsBootstrap(),
|
|
97
|
+
async saveSession(session) {
|
|
98
|
+
if (!supportsOPFS) return false;
|
|
99
|
+
const payload = {
|
|
100
|
+
...session,
|
|
101
|
+
savedAt: new Date().toISOString()
|
|
102
|
+
};
|
|
103
|
+
return saveJSON(SESSION_FILE, payload);
|
|
104
|
+
},
|
|
105
|
+
async loadSession() {
|
|
106
|
+
if (!supportsOPFS) return null;
|
|
107
|
+
return readJSON(SESSION_FILE, null);
|
|
108
|
+
},
|
|
109
|
+
async saveModels(models) {
|
|
110
|
+
if (!supportsOPFS) return false;
|
|
111
|
+
return saveJSON(MODELS_FILE, {
|
|
112
|
+
models,
|
|
113
|
+
savedAt: new Date().toISOString()
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
async loadModels() {
|
|
117
|
+
if (!supportsOPFS) return null;
|
|
118
|
+
return readJSON(MODELS_FILE, null);
|
|
119
|
+
},
|
|
120
|
+
async appendLog(entry) {
|
|
121
|
+
if (!supportsOPFS) return false;
|
|
122
|
+
const text = typeof entry === 'string' ? entry : JSON.stringify(entry);
|
|
123
|
+
return appendLogLine(text);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
registerServiceWorker();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="480" viewBox="0 0 640 480">
|
|
2
|
+
<rect width="640" height="480" rx="36" fill="#fff5df"/>
|
|
3
|
+
<rect x="24" y="24" width="592" height="432" rx="24" fill="none" stroke="#b18a5c" stroke-width="2"/>
|
|
4
|
+
<g fill="#674331" stroke="#9d7250" stroke-width="4">
|
|
5
|
+
<rect x="254" y="84" width="58" height="58" rx="9"/>
|
|
6
|
+
<rect x="326" y="84" width="58" height="58" rx="9"/>
|
|
7
|
+
<rect x="254" y="156" width="58" height="58" rx="9"/>
|
|
8
|
+
<rect x="326" y="156" width="58" height="58" rx="9"/>
|
|
9
|
+
</g>
|
|
10
|
+
<text x="320" y="296" text-anchor="middle" font-family="sans-serif" font-size="48" font-weight="bold" fill="#493322">ChocoDrop</text>
|
|
11
|
+
<text x="320" y="346" text-anchor="middle" font-family="sans-serif" font-size="22" fill="#765f49">Your first drop.</text>
|
|
12
|
+
<circle cx="268" cy="395" r="7" fill="#ae7cbd"/>
|
|
13
|
+
<circle cx="320" cy="395" r="7" fill="#e1aa57"/>
|
|
14
|
+
<circle cx="372" cy="395" r="7" fill="#92b5a0"/>
|
|
15
|
+
</svg>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const CACHE_VERSION = 'chocodrop-pwa-v1';
|
|
2
|
+
const BUNDLE_VERSION = '20251011025008';
|
|
3
|
+
|
|
4
|
+
const APP_SHELL = [
|
|
5
|
+
'/',
|
|
6
|
+
'/index.html',
|
|
7
|
+
'/immersive.html',
|
|
8
|
+
'/manifest.webmanifest',
|
|
9
|
+
'/pwa-bootstrap.js',
|
|
10
|
+
'/immersive-launcher.js',
|
|
11
|
+
'/load-chocodrop.js',
|
|
12
|
+
'/load-three.js',
|
|
13
|
+
'/bootstrap.js',
|
|
14
|
+
'/CommandUI.js',
|
|
15
|
+
'/SceneManager.js',
|
|
16
|
+
'/LiveCommandClient.js',
|
|
17
|
+
'/translation-dictionary.js',
|
|
18
|
+
'/index.js',
|
|
19
|
+
`/chocodrop-demo.umd.min.js?v=${BUNDLE_VERSION}`,
|
|
20
|
+
'/icons/icon-192.png',
|
|
21
|
+
'/icons/icon-512.png',
|
|
22
|
+
'/icons/icon-maskable-512.png'
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
self.addEventListener('install', (event) => {
|
|
26
|
+
event.waitUntil(
|
|
27
|
+
caches.open(CACHE_VERSION).then((cache) => cache.addAll(APP_SHELL)).then(() => self.skipWaiting())
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
self.addEventListener('activate', (event) => {
|
|
32
|
+
event.waitUntil(
|
|
33
|
+
caches.keys().then((keys) =>
|
|
34
|
+
Promise.all(keys.filter((key) => key !== CACHE_VERSION).map((key) => caches.delete(key)))
|
|
35
|
+
).then(() => self.clients.claim())
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function isNavigationRequest(request) {
|
|
40
|
+
return request.mode === 'navigate' ||
|
|
41
|
+
(request.method === 'GET' && request.headers.get('accept')?.includes('text/html'));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function cacheFirst(request) {
|
|
45
|
+
const cache = await caches.open(CACHE_VERSION);
|
|
46
|
+
const cached = await cache.match(request);
|
|
47
|
+
if (cached) {
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
const response = await fetch(request);
|
|
51
|
+
cache.put(request, response.clone());
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function networkFirst(request) {
|
|
56
|
+
const cache = await caches.open(CACHE_VERSION);
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetch(request);
|
|
59
|
+
cache.put(request, response.clone());
|
|
60
|
+
return response;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const cached = await cache.match(request);
|
|
63
|
+
if (cached) {
|
|
64
|
+
return cached;
|
|
65
|
+
}
|
|
66
|
+
if (isNavigationRequest(request)) {
|
|
67
|
+
return cache.match('/immersive.html') || cache.match('/index.html');
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
self.addEventListener('fetch', (event) => {
|
|
74
|
+
const { request } = event;
|
|
75
|
+
if (request.method !== 'GET') {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const url = new URL(request.url);
|
|
80
|
+
|
|
81
|
+
if (url.origin === self.location.origin) {
|
|
82
|
+
if (isNavigationRequest(request)) {
|
|
83
|
+
event.respondWith(networkFirst(request));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (APP_SHELL.includes(url.pathname) || APP_SHELL.includes(url.pathname + url.search)) {
|
|
88
|
+
event.respondWith(cacheFirst(request));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
event.respondWith(cacheFirst(request));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
event.respondWith(
|
|
97
|
+
caches.open(CACHE_VERSION).then((cache) =>
|
|
98
|
+
cache.match(request).then((cached) => {
|
|
99
|
+
const networkFetch = fetch(request)
|
|
100
|
+
.then((response) => {
|
|
101
|
+
if (response && response.status === 200) {
|
|
102
|
+
cache.put(request, response.clone());
|
|
103
|
+
}
|
|
104
|
+
return response;
|
|
105
|
+
})
|
|
106
|
+
.catch(() => cached);
|
|
107
|
+
return cached || networkFetch;
|
|
108
|
+
})
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
self.addEventListener('message', (event) => {
|
|
114
|
+
if (event.data && event.data.type === 'SKIP_WAITING') {
|
|
115
|
+
self.skipWaiting();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChocoDrop 共通翻訳辞書
|
|
3
|
+
* サーバー・クライアント共通で使用する日本語→英語翻訳辞書
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const TRANSLATION_DICTIONARY = {
|
|
7
|
+
// ファンタジー・魔法系
|
|
8
|
+
'ユニコーン': 'unicorn',
|
|
9
|
+
'ドラゴン': 'dragon',
|
|
10
|
+
'龍': 'dragon',
|
|
11
|
+
'怪獣': 'monster',
|
|
12
|
+
'モンスター': 'monster',
|
|
13
|
+
'魔法使い': 'wizard',
|
|
14
|
+
'魔術師': 'sorcerer',
|
|
15
|
+
'魔女': 'witch',
|
|
16
|
+
'妖精': 'fairy',
|
|
17
|
+
'🧚': 'fairy',
|
|
18
|
+
'🧙': 'wizard',
|
|
19
|
+
'魔法杖': 'magic wand',
|
|
20
|
+
'杖': 'wand',
|
|
21
|
+
'スタッフ': 'staff',
|
|
22
|
+
'魔法': 'magic',
|
|
23
|
+
'呪文': 'spell',
|
|
24
|
+
'魔法陣': 'magic circle',
|
|
25
|
+
'水晶玉': 'crystal ball',
|
|
26
|
+
'薬瓶': 'potion bottle',
|
|
27
|
+
'魔導書': 'grimoire',
|
|
28
|
+
'フェニックス': 'phoenix',
|
|
29
|
+
'グリフィン': 'griffin',
|
|
30
|
+
'ペガサス': 'pegasus',
|
|
31
|
+
'ケルベロス': 'cerberus',
|
|
32
|
+
|
|
33
|
+
// 動物
|
|
34
|
+
'画像': 'image',
|
|
35
|
+
'写真': 'photo',
|
|
36
|
+
'イメージ': 'image',
|
|
37
|
+
'絵': 'picture',
|
|
38
|
+
'ファイル': 'file',
|
|
39
|
+
'メディア': 'media',
|
|
40
|
+
'素材': 'asset',
|
|
41
|
+
'動画': 'video',
|
|
42
|
+
'ビデオ': 'video',
|
|
43
|
+
'ムービー': 'movie',
|
|
44
|
+
'映像': 'video',
|
|
45
|
+
'クリップ': 'clip',
|
|
46
|
+
'猫': 'cat',
|
|
47
|
+
'ネコ': 'cat',
|
|
48
|
+
'ねこ': 'cat',
|
|
49
|
+
'犬': 'dog',
|
|
50
|
+
'イヌ': 'dog',
|
|
51
|
+
'いぬ': 'dog',
|
|
52
|
+
'狼': 'wolf',
|
|
53
|
+
'熊': 'bear',
|
|
54
|
+
'ライオン': 'lion',
|
|
55
|
+
'トラ': 'tiger',
|
|
56
|
+
'象': 'elephant',
|
|
57
|
+
'キリン': 'giraffe',
|
|
58
|
+
'シマウマ': 'zebra',
|
|
59
|
+
'パンダ': 'panda',
|
|
60
|
+
'ウサギ': 'rabbit',
|
|
61
|
+
'リス': 'squirrel',
|
|
62
|
+
'ハムスター': 'hamster',
|
|
63
|
+
'フクロウ': 'owl',
|
|
64
|
+
'ワシ': 'eagle',
|
|
65
|
+
'カラス': 'crow',
|
|
66
|
+
'ハト': 'dove',
|
|
67
|
+
'ペンギン': 'penguin',
|
|
68
|
+
'イルカ': 'dolphin',
|
|
69
|
+
'クジラ': 'whale',
|
|
70
|
+
'サメ': 'shark',
|
|
71
|
+
'タコ': 'octopus',
|
|
72
|
+
'カニ': 'crab',
|
|
73
|
+
'エビ': 'shrimp',
|
|
74
|
+
|
|
75
|
+
// 自然・風景
|
|
76
|
+
'花': 'flower',
|
|
77
|
+
'はな': 'flower',
|
|
78
|
+
'ハナ': 'flower',
|
|
79
|
+
'桜': 'cherry blossom',
|
|
80
|
+
'バラ': 'rose',
|
|
81
|
+
'ひまわり': 'sunflower',
|
|
82
|
+
'チューリップ': 'tulip',
|
|
83
|
+
'雲': 'cloud',
|
|
84
|
+
'空': 'sky',
|
|
85
|
+
'海': 'ocean',
|
|
86
|
+
'湖': 'lake',
|
|
87
|
+
'川': 'river',
|
|
88
|
+
'山': 'mountain',
|
|
89
|
+
'やま': 'mountain',
|
|
90
|
+
'ヤマ': 'mountain',
|
|
91
|
+
'森': 'forest',
|
|
92
|
+
'木': 'tree',
|
|
93
|
+
'き': 'tree',
|
|
94
|
+
'キ': 'tree',
|
|
95
|
+
'草原': 'meadow',
|
|
96
|
+
'砂漠': 'desert',
|
|
97
|
+
'滝': 'waterfall',
|
|
98
|
+
'洞窟': 'cave',
|
|
99
|
+
'島': 'island',
|
|
100
|
+
'星座': 'constellation',
|
|
101
|
+
'銀河': 'galaxy',
|
|
102
|
+
'惑星': 'planet',
|
|
103
|
+
|
|
104
|
+
// 建物・場所
|
|
105
|
+
'城': 'castle',
|
|
106
|
+
'しろ': 'castle',
|
|
107
|
+
'シロ': 'castle',
|
|
108
|
+
'宮殿': 'palace',
|
|
109
|
+
'家': 'house',
|
|
110
|
+
'塔': 'tower',
|
|
111
|
+
'教会': 'church',
|
|
112
|
+
'神殿': 'temple',
|
|
113
|
+
'図書館': 'library',
|
|
114
|
+
'学校': 'school',
|
|
115
|
+
'病院': 'hospital',
|
|
116
|
+
'駅': 'station',
|
|
117
|
+
'空港': 'airport',
|
|
118
|
+
'港': 'port',
|
|
119
|
+
'橋': 'bridge',
|
|
120
|
+
'灯台': 'lighthouse',
|
|
121
|
+
'風車': 'windmill',
|
|
122
|
+
'庭': 'garden',
|
|
123
|
+
'公園': 'park',
|
|
124
|
+
'遊園地': 'amusement park',
|
|
125
|
+
|
|
126
|
+
// 乗り物
|
|
127
|
+
'車': 'car',
|
|
128
|
+
'電車': 'train',
|
|
129
|
+
'バス': 'bus',
|
|
130
|
+
'飛行機': 'airplane',
|
|
131
|
+
'ヘリコプター': 'helicopter',
|
|
132
|
+
'船': 'ship',
|
|
133
|
+
'ヨット': 'yacht',
|
|
134
|
+
'自転車': 'bicycle',
|
|
135
|
+
'バイク': 'motorcycle',
|
|
136
|
+
'ロケット': 'rocket',
|
|
137
|
+
|
|
138
|
+
// 天体・時間
|
|
139
|
+
'月': 'moon',
|
|
140
|
+
'太陽': 'sun',
|
|
141
|
+
'星': 'star',
|
|
142
|
+
'彗星': 'comet',
|
|
143
|
+
'流れ星': 'shooting star',
|
|
144
|
+
'虹': 'rainbow',
|
|
145
|
+
'雷': 'lightning',
|
|
146
|
+
'雪': 'snow',
|
|
147
|
+
'雨': 'rain',
|
|
148
|
+
'嵐': 'storm',
|
|
149
|
+
'霧': 'fog',
|
|
150
|
+
'氷': 'ice',
|
|
151
|
+
'火': 'fire',
|
|
152
|
+
'水': 'water',
|
|
153
|
+
'風': 'wind',
|
|
154
|
+
'光': 'light',
|
|
155
|
+
'影': 'shadow',
|
|
156
|
+
'夜': 'night',
|
|
157
|
+
'朝': 'morning',
|
|
158
|
+
'夕方': 'evening',
|
|
159
|
+
|
|
160
|
+
// 色・素材
|
|
161
|
+
'赤': 'red',
|
|
162
|
+
'青': 'blue',
|
|
163
|
+
'緑': 'green',
|
|
164
|
+
'黄色': 'yellow',
|
|
165
|
+
'白': 'white',
|
|
166
|
+
'黒': 'black',
|
|
167
|
+
'紫': 'purple',
|
|
168
|
+
'ピンク': 'pink',
|
|
169
|
+
'オレンジ': 'orange',
|
|
170
|
+
'茶色': 'brown',
|
|
171
|
+
'グレー': 'gray',
|
|
172
|
+
'金': 'gold',
|
|
173
|
+
'銀': 'silver',
|
|
174
|
+
'プラチナ': 'platinum',
|
|
175
|
+
'銅': 'copper',
|
|
176
|
+
'鉄': 'iron',
|
|
177
|
+
'石': 'stone',
|
|
178
|
+
'木材': 'wood',
|
|
179
|
+
'ガラス': 'glass',
|
|
180
|
+
'水晶': 'crystal',
|
|
181
|
+
'ダイヤモンド': 'diamond',
|
|
182
|
+
|
|
183
|
+
// 鳥類
|
|
184
|
+
'鳥': 'bird',
|
|
185
|
+
'とり': 'bird',
|
|
186
|
+
'トリ': 'bird'
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* オブジェクト識別用のエイリアス辞書(拡張版)
|
|
191
|
+
* 翻訳辞書から逆マッピングを生成し、エイリアスも含める
|
|
192
|
+
*/
|
|
193
|
+
function createObjectKeywords() {
|
|
194
|
+
const keywords = {};
|
|
195
|
+
|
|
196
|
+
// 翻訳辞書から逆マッピングを作成
|
|
197
|
+
for (const [japanese, english] of Object.entries(TRANSLATION_DICTIONARY)) {
|
|
198
|
+
// 英語をキーとして日本語とエイリアスを格納
|
|
199
|
+
if (!keywords[japanese]) {
|
|
200
|
+
keywords[japanese] = [];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 英語の翻訳を追加
|
|
204
|
+
if (!keywords[japanese].includes(english)) {
|
|
205
|
+
keywords[japanese].push(english);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return keywords;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 日本語キーワードを英語に翻訳
|
|
214
|
+
*/
|
|
215
|
+
function translateKeyword(japanese) {
|
|
216
|
+
return TRANSLATION_DICTIONARY[japanese] || japanese;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* ファイル名と日本語キーワードをマッチング
|
|
221
|
+
*/
|
|
222
|
+
function matchKeywordWithFilename(keyword, filename, keywords) {
|
|
223
|
+
const lowerFilename = filename.toLowerCase();
|
|
224
|
+
|
|
225
|
+
// 直接マッチ
|
|
226
|
+
if (lowerFilename.includes(keyword.toLowerCase())) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// キーワード辞書での相互マッチ
|
|
231
|
+
for (const [jp, aliases] of Object.entries(keywords)) {
|
|
232
|
+
// キーワードが日本語の場合、対応する英語エイリアスをファイル名で探す
|
|
233
|
+
if (keyword.includes(jp)) {
|
|
234
|
+
for (const alias of aliases) {
|
|
235
|
+
if (lowerFilename.includes(alias.toLowerCase())) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 翻訳辞書での直接マッチ
|
|
243
|
+
const englishKeyword = translateKeyword(keyword);
|
|
244
|
+
if (englishKeyword !== keyword && lowerFilename.includes(englishKeyword.toLowerCase())) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ES6 Modules (クライアント用)
|
|
252
|
+
export { TRANSLATION_DICTIONARY, createObjectKeywords, translateKeyword, matchKeywordWithFilename };
|
|
253
|
+
|
|
254
|
+
// CommonJS (サーバー用)
|
|
255
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
256
|
+
module.exports = { TRANSLATION_DICTIONARY, createObjectKeywords, translateKeyword, matchKeywordWithFilename };
|
|
257
|
+
}
|