手順を導き出す「Logic Editor 3D」



画像

・ダウンロードされる方はこちら。↓

Logic Editor 3D - 操作説明書


このツールは、**「やりたいこと(ゴール)」を指定すると、
手持ちの手段(関数)の中から最適な組み合わせを選び出し、
「手順(スケジュール)」**を自動生成するツールです。

1. 基本的な考え方

このツールでは、データを以下の2種類に分けて管理します。

  1. TYPES (データ型): 「材料」や「状態」のこと。

    • 例:「野菜」「水」「カレー」「報告書」「未処理データ」

  2. FUNCTIONS (関数): 「処理」や「作業」のこと。

    • 例:「野菜を切る(野菜 → カット野菜)」「集計する(データ → 報告書)」

ユーザーが行うのは「材料」と「処理」の登録だけです。
「どの順番でやるか」はAIが自動で考えます。

2. 画面構成

  • 左パネル (Editor): データや関数の登録・削除を行う場所です。

  • 中央パネル (3D View): 自動生成された手順が、左から右へと流れる3Dフローチャートとして表示されます。

  • 右パネル (Result): 実行すべき手順がリスト形式で表示されます。

3. 使い方ステップバイステップ

Step 1: TYPES(データ型)を登録する

  1. 左パネルのタブ 「1. TYPES」 をクリックします。

  2. 入力欄に、作業に登場するモノの名前を入力します。(例:Raw Data)

  3. Add Type ボタンを押します。これを必要な分だけ繰り返します。

Step 2: FUNCTIONS(関数)を登録する

  1. タブ 「2. FUNCTIONS」 をクリックします。

  2. Function Name: 作業の名前を入力します。(例:Clean Data)

  3. Inputs: その作業に必要な材料を選びます。

    • ヒント: Ctrlキー (MacはCommandキー) を押しながらクリックすると、複数の材料を選択できます。

  4. Output: その作業の結果、何ができるかを選びます。

  5. Cost: 作業の大変さを数字で入れます。

    • AIの挙動: 数字が小さい(楽な)作業を優先して選びます。

  6. Register Function ボタンを押して登録します。

Step 3: RUN(実行)する

  1. タブ 「RUN」 をクリックします。

  2. Goal: 最終的に欲しい結果を選びます。(例:Monthly Report)

  3. Find Best Schedule ボタンを押します。

  4. 右パネルに「やるべきリスト」が表示され、中央画面に3D図解が表示されます。

4. ファイル操作について

画面上部のメニューバーから、データの保存や読み込みができます。

  • New: 現在のデータを全て消去し、新規作成します。

  • Open: パソコンに保存した .json ファイルを読み込みます。

  • Save: 現在編集中のファイル名で上書き保存(ダウンロード)します。

  • Save As: 名前を付けてファイルを保存(ダウンロード)します。

※ ブラウザの制約上、「上書き保存」も
「ダウンロード」として動作しますが、ファイル名は引き継がれます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Logic Editor 3D 1.0</title>
    <style>
        /* --- Styles --- */
        body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background-color: #121212; color: #e0e0e0; font-family: 'Segoe UI', Roboto, sans-serif; }
        
        /* Layout */
        #app { display: flex; flex-direction: column; height: 100%; }
        
        /* Menubar */
        #menubar { height: 40px; background: #1e1e1e; border-bottom: 1px solid #333; display: flex; align-items: center; padding: 0 15px; box-shadow: 0 2px 5px rgba(0,0,0,0.5); }
        .brand { font-weight: bold; color: #00e676; margin-right: 20px; letter-spacing: 1px; font-size:1.1em; }
        
        .menu-group { display: flex; gap: 5px; border-right: 1px solid #333; padding-right: 15px; margin-right: 15px; }
        .menu-btn { background: #2d2d2d; border: 1px solid #444; color: #ccc; padding: 5px 12px; cursor: pointer; border-radius: 3px; font-size: 0.85em; transition: 0.2s; }
        .menu-btn:hover { background: #444; color: #fff; border-color: #666; }
        
        .file-info { font-size: 0.85em; color: #888; }
        .file-name { color: #00e676; font-weight: bold; margin-left: 5px; }

        #main-area { display: flex; flex: 1; overflow: hidden; }

        /* Left Panel: Editor */
        #editor-panel { width: 340px; background: #181818; border-right: 1px solid #333; display: flex; flex-direction: column; z-index: 10; }
        .tab-header { display: flex; border-bottom: 1px solid #333; }
        .tab { flex: 1; padding: 12px; text-align: center; cursor: pointer; background: #222; color: #888; font-size: 0.85em; font-weight: bold; border-bottom: 2px solid transparent; }
        .tab:hover { color: #ddd; }
        .tab.active { background: #181818; color: #00e676; border-bottom: 2px solid #00e676; }
        
        .panel-content { flex: 1; overflow-y: auto; padding: 15px; }
        
        .form-group { margin-bottom: 20px; background: #222; padding: 12px; border-radius: 6px; border: 1px solid #333; }
        .form-label { display: block; font-size: 0.75em; color: #aaa; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.5px; }
        input, select { width: 100%; background: #333; border: 1px solid #444; color: #eee; padding: 8px; box-sizing: border-box; border-radius: 4px; margin-bottom: 8px; font-size:0.95em; }
        input:focus, select:focus { border-color: #00e676; outline: none; }
        
        /* List Items */
        .item-list { margin-top: 10px; }
        .db-item { display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #2a2a2a; font-size: 0.9em; background:#1f1f1f; margin-bottom:2px; border-radius:3px;}
        .db-item:hover { background: #2a2a2a; }
        .del-btn { color: #ff5252; cursor: pointer; font-weight: bold; padding: 2px 8px; border-radius:4px; }
        .del-btn:hover { background: rgba(255, 82, 82, 0.1); }

        button.primary { width: 100%; background: #00e676; color: #000; font-weight: bold; border: none; padding: 10px; cursor: pointer; border-radius: 4px; margin-top: 5px; transition:0.2s; }
        button.primary:hover { background: #00c853; }

        /* Center: 3D */
        #canvas-container { flex: 1; position: relative; background: radial-gradient(circle at center, #1a1a1a 0%, #000 100%); overflow: hidden; }
        #overlay { position: absolute; top: 10px; left: 10px; pointer-events: none; }
        .overlay-tag { background: rgba(0,0,0,0.6); color: #00e676; padding: 5px 10px; border-radius: 15px; font-size: 0.8em; border: 1px solid #00e676; }

        /* Right: Results */
        #result-panel { width: 300px; background: #181818; border-left: 1px solid #333; display: flex; flex-direction: column; overflow-y: auto; }
        .step-card { padding: 15px; border-bottom: 1px solid #333; position: relative; background: #1e1e1e; margin-bottom:1px; }
        .step-idx { position: absolute; top: 15px; right: 15px; font-size: 1.5em; color: #333; font-weight: bold; }
        .step-name { font-weight: bold; color: #fff; margin-bottom: 5px; font-size: 1.1em; }
        .step-detail { font-size: 0.85em; color: #aaa; margin-bottom: 5px; }
        .missing-warning { background: rgba(255, 145, 0, 0.1); color: #ff9100; font-size: 0.8em; margin-top: 5px; border: 1px solid #ff9100; padding: 4px 8px; border-radius: 4px; display: inline-block; }

        .hidden { display: none !important; }
    </style>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>

<div id="app">
    <div id="menubar">
        <div class="brand">LOGIC EDITOR</div>
        
        <div class="menu-group">
            <button class="menu-btn" onclick="app.newFile()">New</button>
            <button class="menu-btn" onclick="app.openFile()">Open...</button>
            <button class="menu-btn" onclick="app.saveFile()">Save</button>
            <button class="menu-btn" onclick="app.saveFileAs()">Save As...</button>
        </div>

        <div class="file-info">Editing: <span id="current-filename" class="file-name">Untitled.json</span></div>
        <div style="flex:1"></div>
        <div style="font-size:0.8em; color:#666;">Auto-saved to LocalStorage</div>
    </div>

    <div id="main-area">
        <div id="editor-panel">
            <div class="tab-header">
                <div class="tab active" onclick="app.ui.switchTab('run')">RUN</div>
                <div class="tab" onclick="app.ui.switchTab('types')">1. TYPES</div>
                <div class="tab" onclick="app.ui.switchTab('funcs')">2. FUNCTIONS</div>
            </div>

            <div id="tab-run" class="panel-content">
                <div style="margin-bottom:20px; padding:10px; background:#222; border-left:3px solid #00e676; font-size:0.9em; color:#ccc;">
                    <strong>Process:</strong> Select your desired output. The AI will calculate the most efficient workflow based on your defined functions.
                </div>

                <div class="form-group">
                    <label class="form-label">Goal (Desired Output)</label>
                    <select id="run-goal"></select>
                    <button class="primary" onclick="app.solve()">Find Best Schedule</button>
                </div>
            </div>

            <div id="tab-types" class="panel-content hidden">
                <div class="form-group">
                    <label class="form-label">Define Data/Object Type</label>
                    <input type="text" id="type-name" placeholder="e.g. Raw Data, Monthly Report">
                    <button class="primary" onclick="app.addType()">Add Type</button>
                </div>
                <label class="form-label">Registered Types</label>
                <div id="type-list" class="item-list"></div>
            </div>

            <div id="tab-funcs" class="panel-content hidden">
                <div class="form-group">
                    <label class="form-label">Function Name</label>
                    <input type="text" id="func-name" placeholder="e.g. Analyze Data">
                    
                    <label class="form-label">Inputs (Ctrl+Click for multiple)</label>
                    <select id="func-inputs" multiple style="height:100px;"></select>
                    
                    <label class="form-label">Output (Result)</label>
                    <select id="func-output"></select>

                    <label class="form-label">Cost (Effort/Time)</label>
                    <input type="number" id="func-cost" value="1" min="1">

                    <button class="primary" onclick="app.addFunction()">Register Function</button>
                </div>
                <label class="form-label">Registered Functions</label>
                <div id="func-list" class="item-list"></div>
            </div>
        </div>

        <div id="canvas-container">
            <div id="overlay">
                <span class="overlay-tag" id="status-tag">Ready</span>
            </div>
        </div>

        <div id="result-panel">
            <div style="padding:12px; font-weight:bold; border-bottom:1px solid #333; background:#222; color:#00e676;">OPTIMIZED SCHEDULE</div>
            <div id="schedule-container">
                <div style="padding:30px; color:#555; text-align:center;">
                    Select a goal in the RUN tab to generate a schedule.
                </div>
            </div>
        </div>
    </div>
</div>

<input type="file" id="file-uploader" style="display:none" accept=".json">

<script>
    /**
     * Logic Editor Core
     */
    class App {
        constructor() {
            this.types = [];
            this.functions = [];
            this.currentFileName = "Untitled.json";
            
            this.solver = new LogicSolver(this);
            this.viz = new Visualizer('canvas-container');
            this.ui = new UIManager(this);

            // Load initial data (LocalStorage or Default)
            this.loadLocalStorage();
            this.viz.init();
        }

        // --- File Operations ---

        newFile() {
            if(confirm("Create new file? Unsaved changes will be lost.")) {
                this.types = [];
                this.functions = [];
                this.currentFileName = "Untitled.json";
                this.ui.refreshAll();
                this.viz.clearScene();
                this.ui.updateFilename();
            }
        }

        openFile() {
            document.getElementById('file-uploader').click();
        }

        handleFileSelect(event) {
            const file = event.target.files[0];
            if (!file) return;

            const reader = new FileReader();
            reader.onload = (e) => {
                try {
                    const data = JSON.parse(e.target.result);
                    this.types = data.types || [];
                    this.functions = data.functions || [];
                    this.currentFileName = file.name;
                    this.ui.refreshAll();
                    this.viz.clearScene();
                    this.ui.updateFilename();
                    alert("File loaded successfully.");
                } catch (err) {
                    alert("Error loading file: Invalid format.");
                }
            };
            reader.readAsText(file);
            event.target.value = ''; // Reset input
        }

        saveFile() {
            // "Save" behaves like download, but uses current filename
            this.downloadJSON(this.currentFileName);
        }

        saveFileAs() {
            const name = prompt("Enter file name:", this.currentFileName);
            if (name) {
                this.currentFileName = name.endsWith('.json') ? name : name + '.json';
                this.ui.updateFilename();
                this.downloadJSON(this.currentFileName);
            }
        }

        downloadJSON(filename) {
            const data = JSON.stringify({
                types: this.types,
                functions: this.functions
            }, null, 2);
            
            const blob = new Blob([data], {type: "application/json"});
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            a.click();
            URL.revokeObjectURL(url);
            
            // Also update local storage
            this.saveLocalStorage();
        }

        // --- Local Storage (Auto-save) ---
        loadLocalStorage() {
            const saved = localStorage.getItem('logic_editor_db');
            if (saved) {
                try {
                    const data = JSON.parse(saved);
                    this.types = data.types || [];
                    this.functions = data.functions || [];
                } catch(e) { /* Ignore */ }
            }
            this.ui.refreshAll();
        }

        saveLocalStorage() {
            localStorage.setItem('logic_editor_db', JSON.stringify({
                types: this.types,
                functions: this.functions
            }));
        }

        // --- CRUD Operations ---
        addType() {
            const name = document.getElementById('type-name').value.trim();
            if (!name) return alert("Enter a name.");
            if (this.types.includes(name)) return alert("Type already exists.");
            
            this.types.push(name);
            document.getElementById('type-name').value = '';
            this.saveLocalStorage();
            this.ui.refreshAll();
        }

        deleteType(name) {
            const used = this.functions.some(f => f.output === name || f.inputs.includes(name));
            if (used) return alert("Cannot delete: Type is in use.");
            this.types = this.types.filter(t => t !== name);
            this.saveLocalStorage();
            this.ui.refreshAll();
        }

        addFunction() {
            const name = document.getElementById('func-name').value.trim();
            const inputsEl = document.getElementById('func-inputs');
            const output = document.getElementById('func-output').value;
            const cost = parseInt(document.getElementById('func-cost').value);
            const inputs = Array.from(inputsEl.selectedOptions).map(opt => opt.value);

            if (!name || !output) return alert("Name and Output are required.");

            this.functions.push({
                id: 'func_' + Date.now(),
                name: name,
                inputs: inputs,
                output: output,
                cost: cost || 1
            });
            
            document.getElementById('func-name').value = '';
            this.saveLocalStorage();
            this.ui.refreshAll();
        }

        deleteFunction(id) {
            this.functions = this.functions.filter(f => f.id !== id);
            this.saveLocalStorage();
            this.ui.refreshAll();
        }

        solve() {
            const goal = document.getElementById('run-goal').value;
            if(!goal) return alert("Select a goal first.");

            const solution = this.solver.solve(goal);
            if (!solution) return alert("Impossible to reach this goal.");

            const schedule = this.solver.flatten(solution);
            this.ui.renderSchedule(schedule);
            this.viz.renderGraph(schedule);
        }
    }

    /**
     * Logic Solver
     */
    class LogicSolver {
        constructor(app) { this.app = app; }

        solve(targetType, visited = new Set()) {
            const candidates = this.app.functions.filter(f => f.output === targetType);
            
            if (candidates.length === 0) {
                // Leaf node (Raw Input)
                return this.app.types.includes(targetType) ? null : undefined;
            }

            let bestPath = null;
            let minCost = Infinity;

            for (const func of candidates) {
                if (visited.has(func.id)) continue;
                const newVisited = new Set(visited).add(func.id);
                const inputNodes = [];
                let currentCost = func.cost;
                let possible = true;

                for (const inputType of func.inputs) {
                    const subPath = this.solve(inputType, newVisited);
                    if (subPath === undefined) { possible = false; break; }
                    
                    if (subPath) {
                        currentCost += subPath.totalCost;
                        inputNodes.push(subPath);
                    } else {
                        inputNodes.push({ type: 'INPUT', name: inputType, inputs:[], totalCost:0 });
                    }
                }

                if (possible && currentCost < minCost) {
                    minCost = currentCost;
                    bestPath = { func: func, inputs: inputNodes, totalCost: currentCost };
                }
            }
            return bestPath;
        }

        flatten(node, list = []) {
            if (!node || node.type === 'INPUT') return list;
            node.inputs.forEach(n => this.flatten(n, list));
            if (!list.find(item => item.id === node.func.id)) list.push(node.func);
            return list;
        }
    }

    /**
     * UI Manager
     */
    class UIManager {
        constructor(app) {
            this.app = app;
            document.getElementById('file-uploader').addEventListener('change', (e) => app.handleFileSelect(e));
        }

        switchTab(name) {
            ['run', 'types', 'funcs'].forEach(t => document.getElementById(`tab-${t}`).classList.add('hidden'));
            document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
            document.getElementById(`tab-${name}`).classList.remove('hidden');
            
            // Highlight active tab logic
            const idx = ['run', 'types', 'funcs'].indexOf(name);
            document.querySelectorAll('.tab')[idx].classList.add('active');
        }

        updateFilename() {
            document.getElementById('current-filename').innerText = this.app.currentFileName;
        }

        refreshAll() {
            this.renderTypeList();
            this.renderFuncList();
            this.updateSelects();
        }

        renderTypeList() {
            const el = document.getElementById('type-list');
            el.innerHTML = '';
            this.app.types.forEach(t => {
                const div = document.createElement('div');
                div.className = 'db-item';
                div.innerHTML = `<span>${t}</span> <span class="del-btn" onclick="app.deleteType('${t}')">×</span>`;
                el.appendChild(div);
            });
        }

        renderFuncList() {
            const el = document.getElementById('func-list');
            el.innerHTML = '';
            this.app.functions.forEach(f => {
                const div = document.createElement('div');
                div.className = 'db-item';
                div.innerHTML = `
                    <div style="flex:1">
                        <div style="font-weight:bold; color:#fff;">${f.name}</div>
                        <div style="font-size:0.75em; color:#888;">IN: [${f.inputs.join(', ')}] → OUT: ${f.output} (Cost:${f.cost})</div>
                    </div>
                    <span class="del-btn" onclick="app.deleteFunction('${f.id}')">×</span>
                `;
                el.appendChild(div);
            });
        }

        updateSelects() {
            const goalSel = document.getElementById('run-goal');
            const inSel = document.getElementById('func-inputs');
            const outSel = document.getElementById('func-output');
            [goalSel, inSel, outSel].forEach(s => s.innerHTML = '');

            this.app.types.forEach(t => {
                [goalSel, outSel].forEach(s => {
                    const opt = document.createElement('option');
                    opt.value = t; opt.text = t;
                    s.appendChild(opt);
                });
                const opt = document.createElement('option');
                opt.value = t; opt.text = t;
                inSel.appendChild(opt);
            });
        }

        renderSchedule(list) {
            const container = document.getElementById('schedule-container');
            container.innerHTML = '';
            if(list.length === 0) {
                container.innerHTML = '<div style="padding:20px; text-align:center;">Goal is already achieved (Raw Input).</div>';
                return;
            }

            list.forEach((step, i) => {
                const div = document.createElement('div');
                div.className = 'step-card';
                
                let missingHtml = '';
                const requiredInputs = step.inputs.filter(inp => !list.find(prev => prev.output === inp));
                requiredInputs.forEach(req => missingHtml += `<div class="missing-warning">Input Needed: ${req}</div> `);

                div.innerHTML = `
                    <div class="step-idx">${i+1}</div>
                    <div class="step-name">${step.name}</div>
                    <div class="step-detail">Output: ${step.output}</div>
                    ${missingHtml}
                `;
                container.appendChild(div);
            });
        }
    }

    /**
     * 3D Visualizer
     */
    class Visualizer {
        constructor(domId) {
            this.domId = domId;
            this.scene = null;
            this.meshMap = new Map();
        }

        init() {
            const container = document.getElementById(this.domId);
            this.scene = new THREE.Scene();
            this.scene.background = new THREE.Color(0x0a0a0a);
            this.camera = new THREE.PerspectiveCamera(50, container.clientWidth/container.clientHeight, 0.1, 1000);
            this.camera.position.set(0, 15, 30);
            this.renderer = new THREE.WebGLRenderer({antialias:true});
            this.renderer.setSize(container.clientWidth, container.clientHeight);
            container.appendChild(this.renderer.domElement);
            this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
            
            const grid = new THREE.GridHelper(100, 20, 0x222222, 0x111111);
            this.scene.add(grid);
            this.scene.add(new THREE.AmbientLight(0x404040));
            const light = new THREE.DirectionalLight(0xffffff, 1);
            light.position.set(10,20,10);
            this.scene.add(light);

            window.addEventListener('resize', () => {
                this.camera.aspect = container.clientWidth/container.clientHeight;
                this.camera.updateProjectionMatrix();
                this.renderer.setSize(container.clientWidth, container.clientHeight);
            });
            this.animate();
        }

        clearScene() {
            this.meshMap.forEach(m => this.scene.remove(m));
            this.meshMap.clear();
            this.scene.children.filter(c => c.type==="Line" || c.type==="Sprite").forEach(c => this.scene.remove(c));
        }

        renderGraph(schedule) {
            this.clearScene();
            if(!schedule || schedule.length === 0) return;
            const spacingX = 8;
            
            schedule.forEach((node, i) => {
                const geo = new THREE.BoxGeometry(3, 1, 2);
                const mat = new THREE.MeshLambertMaterial({color: 0x00e676});
                const mesh = new THREE.Mesh(geo, mat);
                mesh.position.set((i * spacingX) - (schedule.length * spacingX / 2), 1, (i%2 === 0 ? 2 : -2));
                
                this.scene.add(mesh);
                this.meshMap.set(node.id, mesh);
                this.addLabel(node.name, mesh.position);

                node.inputs.forEach(inpName => {
                    const provider = schedule.find(prev => prev.output === inpName);
                    if (provider) {
                        const providerMesh = this.meshMap.get(provider.id);
                        if(providerMesh) this.drawLine(providerMesh.position, mesh.position);
                    } else {
                        const inputPos = mesh.position.clone().add(new THREE.Vector3(-2, -2, 0));
                        this.drawLine(inputPos, mesh.position, 0xff9100);
                        this.addLabel(inpName, inputPos, 0.5);
                    }
                });
            });
            this.controls.reset();
        }

        drawLine(p1, p2, color=0x4db8ff) {
            const mat = new THREE.LineBasicMaterial({color: color});
            const geo = new THREE.BufferGeometry().setFromPoints([p1, p2]);
            this.scene.add(new THREE.Line(geo, mat));
        }

        addLabel(text, pos, scale=1.0) {
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            canvas.width = 256; canvas.height = 64;
            ctx.font = "Bold 36px Arial";
            ctx.fillStyle = "white";
            ctx.textAlign = "center";
            ctx.fillText(text, 128, 45);
            const tex = new THREE.CanvasTexture(canvas);
            const sp = new THREE.Sprite(new THREE.SpriteMaterial({map:tex}));
            sp.position.copy(pos);
            sp.position.y += 1.5;
            sp.scale.set(4*scale, 1*scale, 1);
            this.scene.add(sp);
        }

        animate() {
            requestAnimationFrame(() => this.animate());
            this.controls.update();
            this.renderer.render(this.scene, this.camera);
        }
    }

    var app;
    window.onload = () => { app = new App(); };
</script>
</body>
</html>

いいなと思ったら応援しよう!

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
手順を導き出す「Logic Editor 3D」|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1