手順を導き出す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>

コメント