手順を導き出すTaskPath


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

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Task Path 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', sans-serif; }
        
        /* Layout */
        #app { display: flex; flex-direction: column; height: 100%; }
        #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; }
        .menu-btn { background: #333; border: 1px solid #444; color: #ccc; padding: 5px 10px; margin-right: 10px; cursor: pointer; border-radius: 3px; font-size: 0.85em; }
        .menu-btn:hover { background: #444; color: #fff; }
        .spacer { flex: 1; }

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

        /* Left Panel: Editor */
        #editor-panel { width: 320px; 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: 10px; text-align: center; cursor: pointer; background: #222; color: #888; font-size: 0.9em; }
        .tab.active { background: #181818; color: #00e676; font-weight: bold; border-top: 2px solid #00e676; }
        
        .panel-content { flex: 1; overflow-y: auto; padding: 15px; }
        
        .form-group { margin-bottom: 15px; background: #222; padding: 10px; border-radius: 4px; border: 1px solid #333; }
        .form-label { display: block; font-size: 0.75em; color: #aaa; margin-bottom: 5px; text-transform: uppercase; }
        input, select { width: 100%; background: #333; border: 1px solid #444; color: #eee; padding: 6px; box-sizing: border-box; border-radius: 3px; margin-bottom: 5px; }
        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: 6px; border-bottom: 1px solid #2a2a2a; font-size: 0.85em; }
        .db-item:hover { background: #2a2a2a; }
        .del-btn { color: #ff5252; cursor: pointer; font-weight: bold; padding: 0 5px; }

        button.primary { width: 100%; background: #00e676; color: #000; font-weight: bold; border: none; padding: 8px; cursor: pointer; border-radius: 3px; margin-top: 5px; }
        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: 280px; 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; }
        .step-idx { position: absolute; top: 10px; right: 10px; font-size: 1.5em; color: #333; font-weight: bold; }
        .step-name { font-weight: bold; color: #fff; margin-bottom: 5px; }
        .step-detail { font-size: 0.8em; color: #aaa; }
        .missing-warning { color: #ff9100; font-size: 0.8em; margin-top: 5px; border: 1px solid #ff9100; padding: 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>
        <button class="menu-btn" onclick="app.resetData()">Load Sample Data</button>
        <button class="menu-btn" onclick="app.clearData()">Clear All</button>
        <div class="spacer"></div>
        <div style="font-size:0.8em; color:#888;">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')">TYPES</div>
                <div class="tab" onclick="app.ui.switchTab('funcs')">FUNCTIONS</div>
            </div>

            <div id="tab-run" class="panel-content">
                <div class="form-group">
                    <label class="form-label">Select Goal (Output)</label>
                    <select id="run-goal"></select>
                    <button class="primary" onclick="app.solve()">Find Best Schedule</button>
                </div>
                <div style="font-size:0.8em; color:#888; line-height:1.5;">
                    Select the final result you want. The AI will search the database and construct the necessary workflow.
                </div>
            </div>

            <div id="tab-types" class="panel-content hidden">
                <div class="form-group">
                    <label class="form-label">New Data Type Name</label>
                    <input type="text" id="type-name" placeholder="e.g. Raw Excel, Curry">
                    <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. Cook, Analyze">
                    
                    <label class="form-label">Inputs (Hold Ctrl to select multiple)</label>
                    <select id="func-inputs" multiple style="height:80px;"></select>
                    
                    <label class="form-label">Output (Result)</label>
                    <select id="func-output"></select>

                    <label class="form-label">Cost (Effort)</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:10px; font-weight:bold; border-bottom:1px solid #333; background:#222;">Optimization Result</div>
            <div id="schedule-container">
                <div style="padding:20px; color:#555; text-align:center;">No schedule generated.</div>
            </div>
        </div>
    </div>
</div>

<script>
    /**
     * Logic Editor Core
     * Handles Data Management (CRUD), Logic Solving, and UI
     */
    class App {
        constructor() {
            // Data Store
            this.types = [];
            this.functions = [];
            
            // Modules
            this.solver = new LogicSolver(this);
            this.viz = new Visualizer('canvas-container');
            this.ui = new UIManager(this);

            // Init
            this.loadData();
            this.viz.init();
        }

        // --- Data Management ---
        loadData() {
            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) { this.loadSample(); }
            } else {
                this.loadSample();
            }
            this.ui.refreshAll();
        }

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

        loadSample() {
            // Sample: Cooking Curry workflow
            this.types = ["Vegetables", "Meat", "Water", "Roux", "Cut Veggies", "Cut Meat", "Stew", "Curry Rice"];
            this.functions = [
                { id: "f1", name: "Cut Vegetables", inputs: ["Vegetables"], output: "Cut Veggies", cost: 2 },
                { id: "f2", name: "Cut Meat", inputs: ["Meat"], output: "Cut Meat", cost: 2 },
                { id: "f3", name: "Fry & Stew", inputs: ["Cut Veggies", "Cut Meat", "Water"], output: "Stew", cost: 5 },
                { id: "f4", name: "Add Roux", inputs: ["Stew", "Roux"], output: "Curry Rice", cost: 1 },
                // Alternative: Buying it (Cheating/Efficient?)
                { id: "f5", name: "Uber Eats", inputs: [], output: "Curry Rice", cost: 20 } 
            ];
            this.saveData();
        }

        clearData() {
            if(confirm("Delete all types and functions?")) {
                this.types = [];
                this.functions = [];
                this.saveData();
                this.viz.clearScene();
            }
        }

        resetData() {
            if(confirm("Reset to sample data?")) {
                this.loadSample();
            }
        }

        // --- 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.saveData();
        }

        deleteType(name) {
            // Check usage
            const used = this.functions.some(f => f.output === name || f.inputs.includes(name));
            if (used) return alert("Cannot delete: This type is used by a function.");
            
            this.types = this.types.filter(t => t !== name);
            this.saveData();
        }

        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);

            // Get multiple selected inputs
            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
            });
            
            // Reset form
            document.getElementById('func-name').value = '';
            this.saveData();
        }

        deleteFunction(id) {
            this.functions = this.functions.filter(f => f.id !== id);
            this.saveData();
        }

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

            const solution = this.solver.solve(goal);
            
            if (!solution) {
                alert("Impossible to reach this goal with current functions.");
                return;
            }

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

    /**
     * Logic Solver (Backward Chaining with Cost Optimization)
     */
    class LogicSolver {
        constructor(app) {
            this.app = app;
        }

        findProviders(outputType) {
            return this.app.functions.filter(f => f.output === outputType);
        }

        solve(targetType, visited = new Set()) {
            const candidates = this.findProviders(targetType);
            
            // If no function produces this, it might be a Raw Input (User provided)
            if (candidates.length === 0) {
                // Return a "Leaf" node indicating user input required
                // Only if the type exists in DB
                if (this.app.types.includes(targetType)) {
                     // Virtual node for "User Input"
                     return null; // Null means "Must be provided externally"
                }
                return undefined; // Undefined means "Does not exist / Error"
            }

            let bestPath = null;
            let minCost = Infinity;

            for (const func of candidates) {
                if (visited.has(func.id)) continue; // Cycle detection

                const newVisited = new Set(visited);
                newVisited.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;
                    }
                    
                    // subPath is null if it's a raw input (cost 0 or specific cost?)
                    // Let's say raw input has 0 cost for calculation
                    if (subPath) {
                        currentCost += subPath.totalCost;
                        inputNodes.push(subPath);
                    } else {
                        // Raw Input
                        inputNodes.push({ type: 'INPUT', name: inputType, inputs:[], totalCost:0 });
                    }
                }

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

        flatten(node, list = [], depth = 0) {
            if (!node || node.type === 'INPUT') return list;

            node.inputs.forEach(n => this.flatten(n, list, depth + 1));

            if (!list.find(item => item.id === node.func.id)) {
                // Calculate conceptual layer (how far from start)
                let layer = 0;
                node.inputs.forEach(inp => {
                    // find max layer of inputs
                    // This is simplified. True topological sort is better but complex.
                });
                list.push({ ...node.func, _depth: depth });
            }
            return list;
        }
    }

    /**
     * UI Manager
     */
    class UIManager {
        constructor(app) {
            this.app = app;
            this.tabs = ['run', 'types', 'funcs'];
        }

        switchTab(name) {
            this.tabs.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');
            // Find the tab button to active.. simplistic:
            const index = this.tabs.indexOf(name);
            document.querySelectorAll('.tab')[index].classList.add('active');
        }

        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;">${f.inputs.join('+') || 'User'} → ${f.output}</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');

            // Save selection logic omitted for brevity, just rebuild
            [goalSel, inSel, outSel].forEach(s => s.innerHTML = '');

            this.app.types.forEach(t => {
                // Goal & Output
                [goalSel, outSel].forEach(s => {
                    const opt = document.createElement('option');
                    opt.value = t; opt.text = t;
                    s.appendChild(opt);
                });
                // Inputs (Multiple)
                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:10px;">Goal is already achieved (Raw Input).</div>';
                return;
            }

            list.forEach((step, i) => {
                const div = document.createElement('div');
                div.className = 'step-card';
                
                // Identify Missing Inputs (Raw Inputs)
                let missingHtml = '';
                const requiredInputs = step.inputs.filter(inp => !list.find(prev => prev.output === inp));
                if (requiredInputs.length > 0) {
                    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);
            this.controls.enableDamping = true;

            const grid = new THREE.GridHelper(100, 20, 0x222222, 0x111111);
            this.scene.add(grid);
            
            const light = new THREE.DirectionalLight(0xffffff, 1);
            light.position.set(10,20,10);
            this.scene.add(light);
            this.scene.add(new THREE.AmbientLight(0x404040));

            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();
            // Remove lines/sprites
            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;

            // Simple Auto-Layout based on index (Left to Right)
            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);
                
                // Position: X based on order. Z zigzag to prevent overlap visually
                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);

                // Label
                this.addLabel(node.name, mesh.position);

                // Draw lines to dependencies
                // Find previous nodes that provide my inputs
                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 {
                        // User Input (Draw a line to "nowhere" or a special node?)
                        // For now, simple line downwards indicating raw input
                        const inputPos = mesh.position.clone().add(new THREE.Vector3(-2, -2, 0));
                        this.drawLine(inputPos, mesh.position, 0xff9100);
                        this.addLabel(inpName, inputPos, 0.5);
                    }
                });
            });

            // Center camera
            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 mat = new THREE.SpriteMaterial({map:tex});
            const sp = new THREE.Sprite(mat);
            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);
        }
    }

    // Init App
    var app;
    window.onload = () => { app = new App(); };

</script>
</body>
</html>

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

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
小ぶりなプログラムを試しに作っているんですが、 ここではその説明書きをしていこうと思います。 こういう機能をつけてみてほしいだとかいった要望があれば コメント欄に書いてみて下さい。 ひまをみて対応します。
手順を導き出すTaskPath|古井和雄
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