diff --git a/network-poc/static/index.html b/network-poc/static/index.html index 8c5a80d..0e9f115 100644 --- a/network-poc/static/index.html +++ b/network-poc/static/index.html @@ -1247,8 +1247,16 @@ Max ~100 sanaa — pidempi vie konteksti-ikkunaa." style="color:#484f58;cursor:h - - + +
+ + +
@@ -4780,11 +4788,255 @@ ${filesHtml} builderEditing = null; } + function builderExportCrewAI() { + if (builderAgents.length === 0) { alert('Ei agentteja — luo ensin agentteja.'); return; } + + // Rooli-kuvaukset CrewAI:lle + const roleGoals = { + coder: 'Write clean, tested, production-ready code', + qa: 'Find bugs, write tests, ensure quality', + devops: 'Create deployment configs, CI/CD, Docker', + devsecops: 'Security auditing, OWASP compliance, vulnerability scanning', + architect: 'System design, API contracts, architecture decisions', + iac: 'Write infrastructure-as-code (OpenTofu/Terraform HCL)', + data: 'Database design, SQL optimization, data modeling', + manager: 'Break down tasks, coordinate team, prioritize work', + writer: 'Write documentation, READMEs, guides', + custom: 'Complete assigned tasks effectively', + }; + + // crew.py + const agentDefs = builderAgents.map(a => { + const varName = a.id.replace(/[^a-z0-9_]/g, '_'); + const goal = roleGoals[a.role] || roleGoals.custom; + return `${varName} = Agent( + role="${a.name}", + goal="${goal}", + backstory="""${a.prompt.replace(/"/g, '\\"')}""", + llm="ollama/${a.model}", + verbose=True, +)`; + }).join('\n\n'); + + const taskDefs = builderAgents.map((a, i) => { + const varName = a.id.replace(/[^a-z0-9_]/g, '_'); + const goal = roleGoals[a.role] || roleGoals.custom; + return `task_${varName} = Task( + description="${goal} for the project: {project_description}", + expected_output="Completed deliverable from ${a.name}", + agent=${varName}, +)`; + }).join('\n\n'); + + const agentVars = builderAgents.map(a => a.id.replace(/[^a-z0-9_]/g, '_')); + const taskVars = builderAgents.map(a => 'task_' + a.id.replace(/[^a-z0-9_]/g, '_')); + + const crewPy = `""" +Kipinä Agent Crew — generoitu Agent Builderista +https://kipina.studio/#builder +""" +from crewai import Agent, Task, Crew, Process + +# ── Agentit ── + +${agentDefs} + +# ── Tehtävät ── + +${taskDefs} + +# ── Tiimi ── + +crew = Crew( + agents=[${agentVars.join(', ')}], + tasks=[${taskVars.join(', ')}], + process=Process.sequential, + verbose=True, +) + +if __name__ == "__main__": + import sys + project = sys.argv[1] if len(sys.argv) > 1 else "FastAPI + SQLite CRUD API" + result = crew.kickoff(inputs={"project_description": project}) + print(result) +`; + + // pyproject.toml + const pyproject = `[project] +name = "kipina-crew" +version = "0.1.0" +requires-python = ">=3.11" +dependencies = [ + "crewai[tools]>=0.100.0", +] + +[project.scripts] +crew = "crew:crew.kickoff" +`; + + // .env + const models = [...new Set(builderAgents.map(a => a.model))]; + const dotenv = `# Ollama-asetukset +OLLAMA_BASE_URL=http://ollama:11434 +# Mallit jotka tarvitaan: ${models.join(', ')} +`; + + // Dockerfile + const dockerfile = `FROM python:3.12-slim +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv +WORKDIR /app +COPY pyproject.toml ./ +RUN uv sync --no-dev +COPY crew.py .env ./ +CMD ["uv", "run", "python", "crew.py"] +`; + + // docker-compose.yml + const ollamaPulls = models.map(m => `ollama pull ${m}`).join(' && '); + const compose = `services: + ollama: + image: ollama/ollama:latest + container_name: kipina-crew-ollama + ports: + - "11434:11434" + volumes: + - ollama-models:/root/.ollama + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:11434/api/version"] + interval: 5s + timeout: 3s + retries: 10 + + # Ladataan mallit Ollamaan ennen crewn käynnistystä + ollama-init: + image: ollama/ollama:latest + depends_on: + ollama: + condition: service_healthy + entrypoint: ["sh", "-c", "${ollamaPulls}"] + environment: + - OLLAMA_HOST=http://ollama:11434 + + crew: + build: . + container_name: kipina-crew + depends_on: + ollama-init: + condition: service_completed_successfully + environment: + - OLLAMA_BASE_URL=http://ollama:11434 + # Projektin kuvaus argumenttina: + # docker compose run crew uv run python crew.py "FastAPI todo app" + +volumes: + ollama-models: +`; + + // README.md + const readme = `# Kipinä Agent Crew + +Generoitu [Kipinä Agent Builderista](https://kipina.studio/#builder). + +## Agentit + +${builderAgents.map(a => `- **${a.name}** — ${a.role} (${a.model})`).join('\\n')} + +## Käynnistys + +\`\`\`bash +# Docker Compose (suositeltu) — käynnistää Ollaman + lataa mallit + ajaa crewn +docker compose run crew uv run python crew.py "FastAPI + SQLite CRUD API" + +# Tai lokaalisti (vaatii Ollaman käynnissä) +uv sync +uv run python crew.py "FastAPI + SQLite CRUD API" +\`\`\` +`; + + // Genero ZIP käyttäen samaa logiikkaa kuin downloadZip + const files = { + 'crew.py': crewPy, + 'pyproject.toml': pyproject, + '.env': dotenv, + 'Dockerfile': dockerfile, + 'docker-compose.yml': compose, + 'README.md': readme, + }; + + function crc32(bytes) { + let crc = 0xFFFFFFFF; + for (let i = 0; i < bytes.length; i++) { + crc ^= bytes[i]; + for (let j = 0; j < 8; j++) crc = (crc >>> 1) ^ (crc & 1 ? 0xEDB88320 : 0); + } + return (crc ^ 0xFFFFFFFF) >>> 0; + } + + const entries = Object.entries(files); + const parts = []; + const centralDir = []; + let offset = 0; + + for (const [name, content] of entries) { + const nameBytes = new TextEncoder().encode(name); + const contentBytes = new TextEncoder().encode(content || ''); + const crc = crc32(contentBytes); + + const header = new Uint8Array(30 + nameBytes.length); + const hv = new DataView(header.buffer); + hv.setUint32(0, 0x04034b50, true); + hv.setUint16(4, 20, true); + hv.setUint16(8, 0, true); + hv.setUint32(14, crc, true); + hv.setUint32(18, contentBytes.length, true); + hv.setUint32(22, contentBytes.length, true); + hv.setUint16(26, nameBytes.length, true); + header.set(nameBytes, 30); + parts.push(header, contentBytes); + + const cd = new Uint8Array(46 + nameBytes.length); + const cv = new DataView(cd.buffer); + cv.setUint32(0, 0x02014b50, true); + cv.setUint16(4, 20, true); + cv.setUint16(6, 20, true); + cv.setUint16(10, 0, true); + cv.setUint32(16, crc, true); + cv.setUint32(20, contentBytes.length, true); + cv.setUint32(24, contentBytes.length, true); + cv.setUint16(28, nameBytes.length, true); + cv.setUint32(42, offset, true); + cd.set(nameBytes, 46); + centralDir.push(cd); + offset += header.length + contentBytes.length; + } + + const cdOffset = offset; + let cdSize = 0; + for (const cd of centralDir) { parts.push(cd); cdSize += cd.length; } + const eocd = new Uint8Array(22); + const ev = new DataView(eocd.buffer); + ev.setUint32(0, 0x06054b50, true); + ev.setUint16(8, entries.length, true); + ev.setUint16(10, entries.length, true); + ev.setUint32(12, cdSize, true); + ev.setUint32(16, cdOffset, true); + parts.push(eocd); + + const blob = new Blob(parts, { type: 'application/zip' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'kipina-crew.zip'; + a.click(); + URL.revokeObjectURL(url); + } + // Globaalit Builder-funktiot (onclick tarvitsee) window.builderNew = builderNew; window.builderEdit = builderEdit; window.builderSave = builderSave; window.builderDelete = builderDelete; + window.builderExportCrewAI = builderExportCrewAI; window.builderCancel = builderCancel; // Ladataan agentit kun builder-tabi avataan