手順を導き出すTaskPath
・ダウンロードされる方はこちら。↓
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Logic Synthesizer 3D</title>
<style>
/* UI Theme */
body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background-color: #050505; color: #cfcfcf; font-family: 'Segoe UI', monospace; }
#menubar { height: 45px; background: #1a1a1a; display: flex; align-items: center; padding: 0 15px; border-bottom: 1px solid #333; box-shadow: 0 4px 10px rgba(0,0,0,0.5); z-index: 10; }
.brand { font-weight: 700; color: #00e676; margin-right: 20px; font-size: 1.1em; letter-spacing: 1px; }
.control-group { display: flex; align-items: center; gap: 10px; margin-right: 20px; }
select, button { background: #333; border: 1px solid #444; color: #eee; padding: 6px 12px; border-radius: 4px; cursor: pointer; transition: 0.2s; }
select:hover, button:hover { border-color: #00e676; color: #fff; }
button.primary { background: #00e676; color: #000; font-weight: bold; border: none; }
button.primary:hover { background: #00c853; }
#container { display: flex; height: calc(100% - 45px); }
/* Left Panel: Function DB */
#left-panel { width: 280px; background: #111; border-right: 1px solid #222; display: flex; flex-direction: column; z-index: 5; }
.panel-header { padding: 12px; background: #1a1a1a; font-weight: bold; border-bottom: 1px solid #222; color: #888; font-size: 0.8em; text-transform: uppercase; }
#function-list { flex: 1; overflow-y: auto; padding: 5px; }
.func-item { background: #1a1a1a; border: 1px solid #333; margin-bottom: 5px; padding: 8px; border-radius: 4px; font-size: 0.85em; }
.func-name { color: #4db8ff; font-weight: bold; margin-bottom: 4px; }
.func-io { display: flex; justify-content: space-between; color: #888; font-size: 0.8em; }
.io-tag { background: #222; padding: 1px 4px; border-radius: 2px; }
/* Center: 3D Canvas */
#center-panel { flex: 1; position: relative; background: radial-gradient(circle at center, #1a1a1a 0%, #000 100%); }
#canvas-container { width: 100%; height: 100%; outline: none; }
/* Overlay Status */
#status-overlay { position: absolute; bottom: 20px; left: 20px; color: #888; font-size: 0.9em; pointer-events: none; }
/* Right Panel: Solution Details */
#right-panel { width: 300px; background: #111; border-left: 1px solid #222; display: flex; flex-direction: column; overflow-y: auto; z-index: 5; }
.step-item { padding: 15px; border-bottom: 1px solid #222; position: relative; }
.step-num { position: absolute; top: 10px; right: 10px; font-size: 2em; color: #222; font-weight: bold; }
.step-title { font-weight: bold; color: #eee; margin-bottom: 5px; }
.step-desc { font-size: 0.85em; color: #aaa; line-height: 1.4; }
.req-input { color: #ff9100; font-size: 0.85em; margin-top: 5px; }
.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="menubar">
<div class="brand">LOGIC SYNTH 3D</div>
<div class="control-group">
<span style="font-size:0.8em; color:#888;">GOAL:</span>
<select id="goal-select">
</select>
<button class="primary" onclick="app.solve()">AUTO SYNTHESIZE</button>
</div>
<div class="control-group">
<button onclick="app.resetView()">Reset View</button>
</div>
</div>
<div id="container">
<div id="left-panel">
<div class="panel-header">Function Database</div>
<div id="function-list"></div>
</div>
<div id="center-panel">
<div id="canvas-container"></div>
<div id="status-overlay">Select a goal and click Synthesize to generate the logic tree.</div>
</div>
<div id="right-panel">
<div class="panel-header">Execution Schedule</div>
<div id="schedule-list">
<div style="padding:20px; color:#555; text-align:center;">
No solution generated yet.
</div>
</div>
</div>
</div>
<script>
/**
* 1. Data Definitions
* Defining the "Types" and "Functions" available in the database.
*/
const DATA_TYPES = {
RAW_DATA: "Raw CSV",
CLEAN_DATA: "Cleaned Data",
SALES_LIST: "Sales List",
DATE_RANGE: "Date Range",
MONTHLY_REPORT: "Monthly Report",
CHART_IMAGE: "Chart Image",
PDF_REPORT: "PDF File",
EMAIL_DRAFT: "Email Draft",
SENT_STATUS: "Sent Status"
};
// The "Function Database"
// cost: arbitrary unit of effort/time needed to run this function
const FUNCTION_DB = [
{ id: "f1", name: "Load CSV", inputs: [], output: DATA_TYPES.RAW_DATA, cost: 1, desc: "User uploads a CSV file." },
{ id: "f2", name: "Remove Nulls", inputs: [DATA_TYPES.RAW_DATA], output: DATA_TYPES.CLEAN_DATA, cost: 2, desc: "Clean data rows." },
{ id: "f3", name: "Extract Sales", inputs: [DATA_TYPES.CLEAN_DATA], output: DATA_TYPES.SALES_LIST, cost: 2, desc: "Filter sales columns." },
{ id: "f4", name: "Input Date", inputs: [], output: DATA_TYPES.DATE_RANGE, cost: 1, desc: "User inputs start/end date." },
{ id: "f5", name: "Aggregate", inputs: [DATA_TYPES.SALES_LIST, DATA_TYPES.DATE_RANGE], output: DATA_TYPES.MONTHLY_REPORT, cost: 5, desc: "Sum sales by month." },
{ id: "f6", name: "Generate Chart", inputs: [DATA_TYPES.MONTHLY_REPORT], output: DATA_TYPES.CHART_IMAGE, cost: 3, desc: "Create a line chart." },
{ id: "f7", name: "Make PDF", inputs: [DATA_TYPES.MONTHLY_REPORT, DATA_TYPES.CHART_IMAGE], output: DATA_TYPES.PDF_REPORT, cost: 4, desc: "Combine text and chart." },
{ id: "f8", name: "Write Email", inputs: [DATA_TYPES.PDF_REPORT], output: DATA_TYPES.EMAIL_DRAFT, cost: 2, desc: "Draft email with attachment." },
{ id: "f9", name: "Send Email", inputs: [DATA_TYPES.EMAIL_DRAFT], output: DATA_TYPES.SENT_STATUS, cost: 1, desc: "Send via SMTP." },
// Alternative paths (to cause branching choices)
{ id: "f10", name: "Quick Summary", inputs: [DATA_TYPES.RAW_DATA], output: DATA_TYPES.MONTHLY_REPORT, cost: 10, desc: "Rough summary directly from raw data (Low quality)." },
{ id: "f11", name: "Direct Print", inputs: [DATA_TYPES.CHART_IMAGE], output: DATA_TYPES.SENT_STATUS, cost: 5, desc: "Print and mail physically." }
];
/**
* 2. The Logic Engine (Planner)
* Uses Backward Chaining + Simple Heuristic to find the best path.
*/
class LogicSolver {
constructor(db) {
this.db = db;
}
// Find a function that produces a specific output type
findProviders(outputType) {
return this.db.filter(f => f.output === outputType);
}
// Main recursive solver
// Returns a tree node: { function: funcObj, inputs: [childNodes], totalCost: n }
solve(targetType, visited = new Set()) {
// Find all candidates
const candidates = this.findProviders(targetType);
if (candidates.length === 0) return null; // No way to make this
let bestPath = null;
let minCost = Infinity;
for (const func of candidates) {
if (visited.has(func.id)) continue; // Avoid cycles
const newVisited = new Set(visited);
newVisited.add(func.id);
// Solve for inputs
const inputPaths = [];
let possible = true;
let currentCost = func.cost;
for (const inputType of func.inputs) {
// Recursively find how to make this input
const subPath = this.solve(inputType, newVisited);
if (subPath) {
inputPaths.push(subPath);
currentCost += subPath.totalCost;
} else {
// Can't make this input?
// Check if it's a "User Input" (functions with 0 inputs in DB are sources)
// But wait, our DB defines sources as functions with inputs:[]
// So if subPath is null, it means we have NO function to make it.
// In this DB design, if inputType isn't produced by anyone, it's impossible.
possible = false;
break;
}
}
if (possible) {
if (currentCost < minCost) {
minCost = currentCost;
bestPath = {
func: func,
inputs: inputPaths,
totalCost: currentCost
};
}
}
}
return bestPath;
}
// Flatten the tree into a scheduled list (Topological Sort ish)
flatten(node, list = [], depth = 0) {
if (!node) return;
// Add dependencies first
node.inputs.forEach(n => this.flatten(n, list, depth + 1));
// Add self if not already in list
if (!list.find(i => i.func.id === node.func.id)) {
// Assign a "Layer" for visualization (Max depth of dependencies)
// Simplified: We just push to list. The index in list is roughly execution order.
const layer = list.length;
list.push({ ...node.func, layer: layer });
}
return list;
}
}
/**
* 3. Application Controller
*/
class App {
constructor() {
this.solver = new LogicSolver(FUNCTION_DB);
this.viz = new Visualizer();
this.initUI();
}
initUI() {
// Populate Function List
const fList = document.getElementById('function-list');
FUNCTION_DB.forEach(f => {
const el = document.createElement('div');
el.className = 'func-item';
el.innerHTML = `
<div class="func-name">${f.name}</div>
<div class="func-io">
<span>IN: ${f.inputs.length ? f.inputs.join(', ') : 'User'}</span>
<span class="io-tag">OUT: ${f.output}</span>
</div>
`;
fList.appendChild(el);
});
// Populate Goal Select
const gSelect = document.getElementById('goal-select');
Object.values(DATA_TYPES).forEach(type => {
const opt = document.createElement('option');
opt.value = type;
opt.innerText = type;
if(type === DATA_TYPES.SENT_STATUS) opt.selected = true; // Default goal
gSelect.appendChild(opt);
});
this.viz.init();
}
solve() {
const goal = document.getElementById('goal-select').value;
const solutionTree = this.solver.solve(goal);
if (!solutionTree) {
alert("No valid path found to generate this output.");
return;
}
// Convert tree to linear schedule
const schedule = this.solver.flatten(solutionTree);
// Visualize
this.renderSchedule(schedule);
this.viz.renderGraph(schedule, solutionTree);
}
renderSchedule(schedule) {
const container = document.getElementById('schedule-list');
container.innerHTML = '';
schedule.forEach((step, index) => {
const el = document.createElement('div');
el.className = 'step-item';
el.innerHTML = `
<div class="step-num">${index + 1}</div>
<div class="step-title">${step.name}</div>
<div class="step-desc">${step.desc}</div>
${step.inputs.length === 0 ? '<div class="req-input">⚠️ Waiting for User Input</div>' : ''}
`;
container.appendChild(el);
});
}
resetView() {
this.viz.resetCamera();
}
}
/**
* 4. 3D Visualization (Layout Engine)
*/
class Visualizer {
constructor() {
this.container = document.getElementById('canvas-container');
this.scene = null;
this.meshMap = new Map();
}
init() {
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x050505);
// Camera
this.camera = new THREE.PerspectiveCamera(60, this.container.clientWidth/this.container.clientHeight, 0.1, 1000);
this.camera.position.set(0, 10, 30);
// Renderer
this.renderer = new THREE.WebGLRenderer({antialias: true});
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.container.appendChild(this.renderer.domElement);
// Controls
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
// Light
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(10, 20, 10);
this.scene.add(light);
this.scene.add(new THREE.AmbientLight(0x404040));
// Grid
const grid = new THREE.GridHelper(100, 20, 0x222222, 0x111111);
this.scene.add(grid);
// Resize
window.addEventListener('resize', () => {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
});
this.animate();
}
// Render the scheduled graph
renderGraph(flatList, tree) {
// Clear old
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));
// Calculate Layers for 3D positioning (X axis = Time/Step)
// We re-calculate depth properly for layout
const depthMap = new Map();
const setDepth = (node, d) => {
depthMap.set(node.func.id, Math.max(d, depthMap.get(node.func.id) || 0));
node.inputs.forEach(n => setDepth(n, d - 1));
};
// Helper to walk the tree object
const walkTree = (node, d) => {
setDepth(node, d); // Assign abstract depth
};
// Start from the goal (rightmost)
walkTree(tree, 0);
// Normalize depths so start is 0
const minD = Math.min(...depthMap.values());
flatList.forEach(f => {
f._x = (depthMap.get(f.id) - minD) * 8; // Spacing X
f._z = 0; // Will adjust Z to avoid overlap
});
// Resolve overlaps in Z (same layer)
const layerCounts = {};
flatList.forEach(f => {
if(!layerCounts[f._x]) layerCounts[f._x] = 0;
f._z = layerCounts[f._x] * 6 - (layerCounts[f._x]*3); // Crude spacing
if (layerCounts[f._x] % 2 !== 0) f._z *= -1; // Zigzag
layerCounts[f._x]++;
});
// Draw Nodes
const geo = new THREE.BoxGeometry(3, 1, 2);
flatList.forEach(f => {
const color = f.inputs.length === 0 ? 0xff9100 : 0x00e676; // Orange for Input, Green for Process
const mat = new THREE.MeshLambertMaterial({color: color});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(f._x - 20, 0.5, f._z); // Offset to center
this.scene.add(mesh);
this.meshMap.set(f.id, mesh);
// Label
this.addLabel(f.name, mesh.position);
});
// Draw Links (Dependencies)
const lineMat = new THREE.LineBasicMaterial({color: 0x4db8ff});
// We need to draw lines based on inputs described in the flat list
// But the flat list functions don't reference the *specific instances* in the tree easily
// So we loop the flat list and find providers in the flat list
flatList.forEach(consumer => {
const consumerMesh = this.meshMap.get(consumer.id);
// Who provides my inputs?
consumer.inputs.forEach(inputType => {
// In this simple model, we look for the node in flatList that outputs this type
// Note: If multiple output same type, LogicSolver picked one.
// For visualization simplicity, we just find the one that exists in our schedule.
const provider = flatList.find(p => p.output === inputType && p._x < consumer._x);
if (provider) {
const providerMesh = this.meshMap.get(provider.id);
const pts = [];
pts.push(providerMesh.position.clone());
// Add a "elbow" point for nicer routing? For now straight line.
pts.push(consumerMesh.position.clone());
const g = new THREE.BufferGeometry().setFromPoints(pts);
this.scene.add(new THREE.Line(g, lineMat));
}
});
});
}
addLabel(text, pos) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 256; canvas.height = 64;
ctx.font = "Bold 40px Arial";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.fillText(text, 128, 48);
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(6, 1.5, 1);
this.scene.add(sp);
}
resetCamera() {
this.camera.position.set(0, 10, 30);
this.controls.reset();
}
animate() {
requestAnimationFrame(() => this.animate());
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
}
const app = new App();
</script>
</body>
</html>

コメント