Export CrewAI: generoi ZIP-projektin agenttitiimistä
Export-nappi Agent Builderissa generoi kipina-crew.zip sisältäen: - crew.py: CrewAI agentit, tehtävät ja sequential pipeline - Dockerfile: Python 3.12 + uv - docker-compose.yml: Ollama + healthcheck + mallilataus + crew - pyproject.toml: crewai[tools] riippuvuus - .env: Ollama-asetukset - README.md: käynnistysohjeet Käynnistys: docker compose run crew uv run python crew.py "projekti" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1247,8 +1247,16 @@ Max ~100 sanaa — pidempi vie konteksti-ikkunaa." style="color:#484f58;cursor:h
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Uusi agentti -nappi -->
|
<!-- Napit -->
|
||||||
<button onclick="builderNew()" id="builder-new-btn" style="margin-top:12px;background:#238636;border:1px solid #2ea043;color:#fff;padding:8px 20px;border-radius:6px;cursor:pointer;font-size:14px">+ Uusi agentti</button>
|
<div style="display:flex;gap:10px;margin-top:12px;flex-wrap:wrap">
|
||||||
|
<button onclick="builderNew()" id="builder-new-btn" style="background:#238636;border:1px solid #2ea043;color:#fff;padding:8px 20px;border-radius:6px;cursor:pointer;font-size:14px">+ Uusi agentti</button>
|
||||||
|
<button onclick="builderExportCrewAI()" style="background:#0d1117;border:1px solid #58a6ff;color:#58a6ff;padding:8px 20px;border-radius:6px;cursor:pointer;font-size:14px" class="builder-tip" data-tip="Generoi koko agenttitiimistä CrewAI Python -projektin
|
||||||
|
ZIP-paketti sisältäen:
|
||||||
|
• crew.py — agentit, tehtävät ja pipeline
|
||||||
|
• Dockerfile + docker-compose.yml
|
||||||
|
• pyproject.toml riippuvuudet
|
||||||
|
• .env mallipohja">Export CrewAI</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -4780,11 +4788,255 @@ ${filesHtml}
|
|||||||
builderEditing = null;
|
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)
|
// Globaalit Builder-funktiot (onclick tarvitsee)
|
||||||
window.builderNew = builderNew;
|
window.builderNew = builderNew;
|
||||||
window.builderEdit = builderEdit;
|
window.builderEdit = builderEdit;
|
||||||
window.builderSave = builderSave;
|
window.builderSave = builderSave;
|
||||||
window.builderDelete = builderDelete;
|
window.builderDelete = builderDelete;
|
||||||
|
window.builderExportCrewAI = builderExportCrewAI;
|
||||||
window.builderCancel = builderCancel;
|
window.builderCancel = builderCancel;
|
||||||
|
|
||||||
// Ladataan agentit kun builder-tabi avataan
|
// Ladataan agentit kun builder-tabi avataan
|
||||||
|
|||||||
Reference in New Issue
Block a user