Compare commits
10 Commits
529a30a6e1
...
b8e8a83e49
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e8a83e49 | ||
|
|
3d6914974d | ||
|
|
9aff2ec154 | ||
|
|
ecd4525a7f | ||
|
|
7a3e5278b9 | ||
|
|
8dcf269b42 | ||
|
|
cb16f35265 | ||
|
|
b9d340b4b4 | ||
|
|
dd07e536f0 | ||
|
|
9af481a022 |
@@ -1,47 +1,57 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM rust:slim AS builder
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
# --- Vaihe 1: Frontend (Astro) ---
|
||||||
curl pkg-config libssl-dev g++ \
|
FROM node:22-slim AS frontend
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
WORKDIR /app/frontend
|
||||||
|
COPY frontend/package.json frontend/package-lock.json* ./
|
||||||
|
RUN npm install --silent
|
||||||
|
COPY frontend/ .
|
||||||
|
COPY frontend/public/pkg public/pkg
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# --- Vaihe 2: Wasm (wasm-pack) ---
|
||||||
|
FROM rust:slim AS wasm-builder
|
||||||
|
RUN apt-get update && apt-get install -y curl pkg-config libssl-dev g++ && rm -rf /var/lib/apt/lists/*
|
||||||
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
|
COPY node/Cargo.toml node/Cargo.toml
|
||||||
|
COPY node/src node/src
|
||||||
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
|
--mount=type=cache,target=/app/target \
|
||||||
|
cd node && wasm-pack build --target web --out-dir /app/wasm-pkg
|
||||||
|
|
||||||
# Kopioi kaikki Cargo-tiedostot
|
# --- Vaihe 3: Hub (Rust) ---
|
||||||
COPY Cargo.toml ./
|
FROM rust:slim AS hub-builder
|
||||||
COPY Cargo.lock* ./
|
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
|
||||||
|
WORKDIR /app
|
||||||
|
COPY Cargo.toml Cargo.lock* ./
|
||||||
COPY hub/Cargo.toml hub/Cargo.toml
|
COPY hub/Cargo.toml hub/Cargo.toml
|
||||||
|
COPY hub/src hub/src
|
||||||
|
# Tarvitaan dummy-cratet jotta workspace kompiloi
|
||||||
COPY node/Cargo.toml node/Cargo.toml
|
COPY node/Cargo.toml node/Cargo.toml
|
||||||
COPY native-node/Cargo.toml native-node/Cargo.toml
|
COPY native-node/Cargo.toml native-node/Cargo.toml
|
||||||
COPY cli/Cargo.toml cli/Cargo.toml
|
COPY cli/Cargo.toml cli/Cargo.toml
|
||||||
|
RUN mkdir -p node/src native-node/src cli/src && touch node/src/lib.rs native-node/src/main.rs cli/src/main.rs
|
||||||
# Kopioi lähdekoodi
|
|
||||||
COPY hub/src hub/src
|
|
||||||
COPY node/src node/src
|
|
||||||
COPY native-node/src native-node/src
|
|
||||||
COPY cli/src cli/src
|
|
||||||
COPY static static
|
|
||||||
|
|
||||||
# Rakenna Wasm — cache mount pitää Cargo-rekisterin ja target-kansion buildien välillä
|
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
||||||
--mount=type=cache,target=/app/target \
|
|
||||||
cd node && wasm-pack build --target web --out-dir ../static/pkg
|
|
||||||
|
|
||||||
# Rakenna Hub
|
|
||||||
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
||||||
--mount=type=cache,target=/app/target \
|
--mount=type=cache,target=/app/target \
|
||||||
cargo build --release -p hub \
|
cargo build --release -p hub \
|
||||||
&& cp /app/target/release/hub /usr/local/bin/hub
|
&& cp /app/target/release/hub /usr/local/bin/hub
|
||||||
|
|
||||||
|
# --- Vaihe 4: Tuotantoimage ---
|
||||||
FROM debian:bookworm-slim
|
FROM debian:bookworm-slim
|
||||||
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY --from=builder /usr/local/bin/hub /usr/local/bin/hub
|
COPY --from=hub-builder /usr/local/bin/hub /usr/local/bin/hub
|
||||||
COPY --from=builder /app/static /app/static
|
COPY --from=frontend /app/frontend/dist /app/frontend/dist
|
||||||
|
COPY --from=wasm-builder /app/wasm-pkg /app/frontend/dist/pkg
|
||||||
|
|
||||||
|
# Kopioidaan GUIDE.md ja templates
|
||||||
|
COPY frontend/public/GUIDE.md /app/frontend/dist/GUIDE.md
|
||||||
|
COPY frontend/public/templates /app/frontend/dist/templates
|
||||||
|
COPY frontend/public/avatars /app/frontend/dist/avatars
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV STATIC_DIR=/app/static
|
ENV STATIC_DIR=/app/frontend/dist
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["hub"]
|
CMD ["hub"]
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
"pyproject.toml": {
|
"pyproject.toml": {
|
||||||
"description": "Project dependencies",
|
"description": "Project dependencies",
|
||||||
"example": "[project]\nname = \"myapp\"\nversion = \"0.1.0\"\nrequires-python = \">=3.11\"\ndependencies = [\n \"fastapi\",\n \"uvicorn[standard]\",\n \"sqlalchemy\",\n]\n\n[project.scripts]\ndev = \"uvicorn main:app --reload\"",
|
"example": "[project]\nname = \"myapp\"\nversion = \"0.1.0\"\nrequires-python = \">=3.11\"\ndependencies = [\n \"fastapi\",\n \"uvicorn[standard]\",\n \"sqlalchemy\",\n]\n\n[project.scripts]\ndev = \"uvicorn main:app --reload\"",
|
||||||
"instructions": "List the exact dependencies needed. Use [project.scripts] for run commands."
|
"instructions": "Use [project] format (PEP 621, compatible with uv). List dependencies under [project.dependencies]. Add [project.scripts] with dev command. Never use requirements.txt or Poetry format. Run with: uv run uvicorn main:app --reload"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"order": ["models.py", "schemas.py", "main.py", "pyproject.toml"]
|
"order": ["models.py", "schemas.py", "main.py", "pyproject.toml"]
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<!-- System prompt -->
|
<!-- System prompt -->
|
||||||
<div style="margin-bottom:10px" title="Agentin perusohje joka lähetetään kielimallille jokaisessa pyynnössä. Hyvän promptin rakenne: 1. Rooli: 'You are an expert...' 2. Säännöt: RULES/CRITICAL RULES listana 3. Esimerkit: EXAMPLE OUTPUT 4. Kiellot: NEVER-lista Vinkki: käytä englantia — malli ymmärtää sen paremmin ja se kuluttaa vähemmän tokeneita.">
|
<div style="margin-bottom:10px" title="Agentin perusohje joka lähetetään kielimallille jokaisessa pyynnössä. Hyvän promptin rakenne: 1. Rooli: 'You are an expert...' 2. Säännöt: RULES/CRITICAL RULES listana 3. Esimerkit: EXAMPLE OUTPUT 4. Kiellot: NEVER-lista Vinkki: käytä englantia — malli ymmärtää sen paremmin ja se kuluttaa vähemmän tokeneita.">
|
||||||
<label style="font-size:12px;color:#8b949e;display:block;margin-bottom:4px;cursor:help">System prompt 💡</label>
|
<label style="font-size:12px;color:#8b949e;display:block;margin-bottom:4px;cursor:help">System prompt 💡</label>
|
||||||
<textarea id="config-prompt" rows="8" style="width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:8px;font-size:13px;font-family:'Courier New',monospace;resize:vertical" placeholder="Kuvaa agentin rooli ja käyttäytyminen..."></textarea>
|
<textarea id="config-prompt" style="width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:8px;font-size:13px;font-family:'Courier New',monospace;resize:vertical;overflow:hidden;min-height:60px" placeholder="Kuvaa agentin rooli ja käyttäytyminen..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sampling-parametrit -->
|
<!-- Sampling-parametrit -->
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ NEVER:
|
|||||||
- Add explanations or comments like "# Add routes here"
|
- Add explanations or comments like "# Add routes here"
|
||||||
- Leave placeholder code or TODO comments
|
- Leave placeholder code or TODO comments
|
||||||
- Use Flask syntax (app.run) in FastAPI projects
|
- Use Flask syntax (app.run) in FastAPI projects
|
||||||
- Forget to import from other project files` },
|
- Forget to import from other project files
|
||||||
|
- Use requirements.txt or Poetry — always use pyproject.toml with [project] format (PEP 621)
|
||||||
|
- Use pip install — use uv (e.g. uv run uvicorn main:app --reload)` },
|
||||||
data: { name: 'Data', avatar: '/avatars/pesukarhu_notext.png', model: 'qwen-coder', order: 2,
|
data: { name: 'Data', avatar: '/avatars/pesukarhu_notext.png', model: 'qwen-coder', order: 2,
|
||||||
temperature: 0.5, topK: 40, repeatPenalty: 1.15, maxTokens: 1024,
|
temperature: 0.5, topK: 40, repeatPenalty: 1.15, maxTokens: 1024,
|
||||||
prompt: `You are a database architect specializing in SQLAlchemy and relational databases.
|
prompt: `You are a database architect specializing in SQLAlchemy and relational databases.
|
||||||
@@ -138,16 +140,18 @@ TEST STRUCTURE:
|
|||||||
ALWAYS: from fastapi.testclient import TestClient` },
|
ALWAYS: from fastapi.testclient import TestClient` },
|
||||||
tester: { name: 'DevOps', avatar: '/avatars/laiskiainen_notext.png', model: 'qwen-coder', order: 4,
|
tester: { name: 'DevOps', avatar: '/avatars/laiskiainen_notext.png', model: 'qwen-coder', order: 4,
|
||||||
temperature: 0.3, topK: 40, repeatPenalty: 1.1, maxTokens: 512,
|
temperature: 0.3, topK: 40, repeatPenalty: 1.1, maxTokens: 512,
|
||||||
prompt: `You are a strict code reviewer. Review the provided code and check for these issues:
|
prompt: `You are a strict code reviewer and static analysis expert. Analyze the code line by line.
|
||||||
|
|
||||||
CHECKLIST:
|
STATIC ANALYSIS CHECKLIST:
|
||||||
1. ✓ All imports exist (no missing "from X import Y")
|
1. IMPORTS: Every "from X import Y" must match an actual export in file X
|
||||||
2. ✓ Import names match: if models.py exports "User", main.py imports "User" (not "UserModel")
|
2. NAMES: Pydantic schemas (UserCreate) must not shadow SQLAlchemy models (User)
|
||||||
3. ✓ Pydantic schema names don't conflict with SQLAlchemy model names
|
3. TYPES: All function parameters have type hints, return types specified
|
||||||
4. ✓ All CRUD endpoints have error handling (404 for not found)
|
4. ERRORS: Every db query that can return None has a 404 check
|
||||||
5. ✓ Database session is properly closed (get_db with yield + finally)
|
5. RESOURCES: Database session uses yield+finally pattern (no leaks)
|
||||||
6. ✓ Response models are specified for type safety
|
6. SECURITY: No raw SQL, no hardcoded secrets, inputs validated via Pydantic
|
||||||
7. ✓ No placeholder comments like "# Add routes here"
|
7. ENDPOINTS: All CRUD operations exist (POST/GET/GET-by-id/PUT/DELETE)
|
||||||
|
8. MODELS: Pydantic Config has from_attributes=True, uses model_dump() not dict()
|
||||||
|
9. COMPLETENESS: No placeholder comments, no "TODO", no "pass" in handlers
|
||||||
|
|
||||||
RESPOND:
|
RESPOND:
|
||||||
- If all checks pass: "LGTM"
|
- If all checks pass: "LGTM"
|
||||||
@@ -254,7 +258,11 @@ OUTPUT FORMAT:
|
|||||||
document.getElementById('config-name').value = a.name;
|
document.getElementById('config-name').value = a.name;
|
||||||
document.getElementById('config-role').textContent = key;
|
document.getElementById('config-role').textContent = key;
|
||||||
document.getElementById('config-model').value = a.model;
|
document.getElementById('config-model').value = a.model;
|
||||||
document.getElementById('config-prompt').value = a.prompt || '';
|
const promptEl = document.getElementById('config-prompt');
|
||||||
|
promptEl.value = a.prompt || '';
|
||||||
|
// Auto-resize: textarea kasvaa sisällön mukaan
|
||||||
|
promptEl.style.height = 'auto';
|
||||||
|
promptEl.style.height = promptEl.scrollHeight + 'px';
|
||||||
|
|
||||||
// Sampling-parametrit
|
// Sampling-parametrit
|
||||||
const tempEl = document.getElementById('config-temperature');
|
const tempEl = document.getElementById('config-temperature');
|
||||||
@@ -281,7 +289,7 @@ OUTPUT FORMAT:
|
|||||||
// Muutosten tallennus
|
// Muutosten tallennus
|
||||||
document.getElementById('config-name').oninput = () => { agents[key].name = document.getElementById('config-name').value; saveAgents(); renderAgentBar(); };
|
document.getElementById('config-name').oninput = () => { agents[key].name = document.getElementById('config-name').value; saveAgents(); renderAgentBar(); };
|
||||||
document.getElementById('config-model').onchange = () => { agents[key].model = document.getElementById('config-model').value; saveAgents(); };
|
document.getElementById('config-model').onchange = () => { agents[key].model = document.getElementById('config-model').value; saveAgents(); };
|
||||||
document.getElementById('config-prompt').oninput = () => { agents[key].prompt = document.getElementById('config-prompt').value; saveAgents(); };
|
promptEl.oninput = () => { agents[key].prompt = promptEl.value; saveAgents(); promptEl.style.height = 'auto'; promptEl.style.height = promptEl.scrollHeight + 'px'; };
|
||||||
tempEl.oninput = () => { agents[key].temperature = +tempEl.value; tempValEl.textContent = tempEl.value; saveAgents(); };
|
tempEl.oninput = () => { agents[key].temperature = +tempEl.value; tempValEl.textContent = tempEl.value; saveAgents(); };
|
||||||
maxtokEl.oninput = () => { agents[key].maxTokens = +maxtokEl.value; maxtokValEl.textContent = maxtokEl.value; saveAgents(); };
|
maxtokEl.oninput = () => { agents[key].maxTokens = +maxtokEl.value; maxtokValEl.textContent = maxtokEl.value; saveAgents(); };
|
||||||
topkEl.oninput = () => { agents[key].topK = +topkEl.value; topkValEl.textContent = topkEl.value; saveAgents(); };
|
topkEl.oninput = () => { agents[key].topK = +topkEl.value; topkValEl.textContent = topkEl.value; saveAgents(); };
|
||||||
@@ -724,7 +732,6 @@ OUTPUT FORMAT:
|
|||||||
|
|
||||||
async function kpnProject(task) {
|
async function kpnProject(task) {
|
||||||
const cdr = agents.coder || Object.values(agents)[1];
|
const cdr = agents.coder || Object.values(agents)[1];
|
||||||
const tst = agents.tester || Object.values(agents)[2];
|
|
||||||
|
|
||||||
// Etsitään sopivin mallipohja
|
// Etsitään sopivin mallipohja
|
||||||
const template = Object.values(templates)[0]; // Toistaiseksi vain FastAPI CRUD
|
const template = Object.values(templates)[0]; // Toistaiseksi vain FastAPI CRUD
|
||||||
@@ -791,43 +798,74 @@ OUTPUT FORMAT:
|
|||||||
const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
const allCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
||||||
let stepN = template.order.length + 1;
|
let stepN = template.order.length + 1;
|
||||||
|
|
||||||
// DevOps/Testaaja: koodikatselmointi
|
// Review-korjausluuppi: max 2 kierrosta
|
||||||
const tst = agents.tester || Object.values(agents)[4];
|
const tst = agents.tester || Object.values(agents)[4];
|
||||||
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — koodikatselmointi`);
|
const MAX_REVIEW_ROUNDS = 3;
|
||||||
highlightAgent('tester');
|
|
||||||
explainStep('Koodikatselmointi', `${tst.name} tarkistaa importit, nimeämiset, virheenkäsittelyn ja tiedostojen yhteensopivuuden.`);
|
|
||||||
const tstPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${allCode}`;
|
|
||||||
const review = await kpnRun(tst.model, tstPrompt);
|
|
||||||
stepN++;
|
|
||||||
|
|
||||||
// Korjausluuppi (jos tarpeen)
|
for (let round = 0; round < MAX_REVIEW_ROUNDS; round++) {
|
||||||
if (review && !review.toLowerCase().includes('lgtm')) {
|
const currentCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
||||||
termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — korjaukset`);
|
|
||||||
highlightAgent('coder');
|
// DevOps review
|
||||||
explainStep('Korjausluuppi', `${tst.name} löysi ongelmia. ${cdr.name} saa palautteen ja korjaa koodin.`);
|
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — koodikatselmointi${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
|
||||||
await kpnRun(cdr.model, `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Fix these issues:\n${review}\n\nCurrent code:\n${allCode}\n\nWrite the corrected files.`);
|
highlightAgent('tester');
|
||||||
|
if (round === 0) explainStep('Koodikatselmointi', `${tst.name} analysoi koodin rivi riviltä: importit, nimeämiset, virheenkäsittely, tietoturva.`);
|
||||||
|
else explainStep('Uudelleentarkistus', `${tst.name} tarkistaa korjaukset.`);
|
||||||
|
|
||||||
|
const reviewPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') + `Review this project:\n\n${currentCode}`;
|
||||||
|
const review = await kpnRun(tst.model, reviewPrompt);
|
||||||
stepN++;
|
stepN++;
|
||||||
}
|
|
||||||
|
|
||||||
// QA: testit
|
// LGTM → ei korjauksia tarvita
|
||||||
|
if (!review || review.toLowerCase().includes('lgtm')) {
|
||||||
|
termLog(` <span style="color:#3fb950">✓ ${esc(tst.name)}: LGTM</span>`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Korjaukset
|
||||||
|
termLog(`\n<span style="color:#d29922;font-weight:bold">[${stepN}] ${esc(cdr.name)}</span> — korjaukset${round > 0 ? ' (kierros '+(round+1)+')' : ''}`);
|
||||||
|
highlightAgent('coder');
|
||||||
|
explainStep('Korjaus', `${tst.name} löysi ongelmia. ${cdr.name} saa palautteen ja korjaa.`);
|
||||||
|
|
||||||
|
const fixPrompt = `${cdr.prompt ? cdr.prompt+'\n\n' : ''}Fix these issues:\n${review}\n\nCurrent code:\n${currentCode}\n\nWrite ALL corrected files. Start each file with: --- filename.py ---`;
|
||||||
|
const fixedCode = await kpnRun(cdr.model, fixPrompt);
|
||||||
|
|
||||||
|
// Parsitaan korjatut tiedostot takaisin files-objektiin
|
||||||
|
if (fixedCode) {
|
||||||
|
const fixedParts = fixedCode.split(/^---\s*(\S+)\s*---$/m);
|
||||||
|
for (let j = 1; j < fixedParts.length; j += 2) {
|
||||||
|
const fname = fixedParts[j].trim();
|
||||||
|
const fcode = (fixedParts[j+1] || '').trim();
|
||||||
|
if (fname && fcode && files[fname] !== undefined) {
|
||||||
|
files[fname] = fcode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stepN++;
|
||||||
|
} // for review round
|
||||||
|
|
||||||
|
// Päivitetään allCode korjausten jälkeen
|
||||||
|
const updatedCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
||||||
|
|
||||||
|
// QA: testit (saa korjatut tiedostot)
|
||||||
const qaAgent = agents.qa || Object.values(agents)[3];
|
const qaAgent = agents.qa || Object.values(agents)[3];
|
||||||
if (qaAgent) {
|
if (qaAgent) {
|
||||||
termLog(`\n<span style="color:#d2a8ff;font-weight:bold">[${stepN}] ${esc(qaAgent.name)}</span> — testit`);
|
termLog(`\n<span style="color:#d2a8ff;font-weight:bold">[${stepN}] ${esc(qaAgent.name)}</span> — testit`);
|
||||||
highlightAgent('qa');
|
highlightAgent('qa');
|
||||||
explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit kaikille endpointeille.`);
|
explainStep('Testit', `${qaAgent.name} kirjoittaa pytest-testit korjatulle koodille.`);
|
||||||
const qaPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Write pytest tests for this project:\n\n${allCode}\n\nWrite a complete test_main.py file with TestClient.`;
|
const qaPrompt = (qaAgent.prompt ? qaAgent.prompt+'\n\n' : '') + `Write pytest tests for this project:\n\n${updatedCode}\n\nWrite a complete test_main.py file with TestClient.`;
|
||||||
const tests = await kpnRun(qaAgent.model, qaPrompt);
|
const tests = await kpnRun(qaAgent.model, qaPrompt);
|
||||||
if (tests) files['test_main.py'] = tests;
|
if (tests) files['test_main.py'] = tests;
|
||||||
stepN++;
|
stepN++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevOps: Dockerfile
|
// DevOps: Dockerfile (saa kaikki tiedostot mukaan lukien testit)
|
||||||
|
const allFilesNow = Object.keys(files).join(', ');
|
||||||
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — Dockerfile`);
|
termLog(`\n<span style="color:var(--accent);font-weight:bold">[${stepN}] ${esc(tst.name)}</span> — Dockerfile`);
|
||||||
highlightAgent('tester');
|
highlightAgent('tester');
|
||||||
explainStep('Dockerfile', `${tst.name} generoi Docker-kontin joka pakkaa projektin ajettavaksi.`);
|
explainStep('Dockerfile', `${tst.name} generoi Docker-kontin kaikista ${Object.keys(files).length} tiedostosta: ${allFilesNow}`);
|
||||||
const dockerPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') +
|
const dockerPrompt = (tst.prompt ? tst.prompt+'\n\n' : '') +
|
||||||
`Write a Dockerfile for this Python FastAPI project.\n\n` +
|
`Write a Dockerfile for this Python FastAPI project.\n\n` +
|
||||||
`Project files: ${Object.keys(files).join(', ')}\n\n` +
|
`Project files: ${allFilesNow}\n\n` +
|
||||||
`Requirements:\n` +
|
`Requirements:\n` +
|
||||||
`- Use python:3.12-slim as base\n` +
|
`- Use python:3.12-slim as base\n` +
|
||||||
`- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv\n` +
|
`- Install uv: COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv\n` +
|
||||||
@@ -846,15 +884,29 @@ OUTPUT FORMAT:
|
|||||||
highlightAgent('observer');
|
highlightAgent('observer');
|
||||||
explainStep('Raportti', `${obs.name} kokoaa yhteenvedon ja antaa arvosanan.`);
|
explainStep('Raportti', `${obs.name} kokoaa yhteenvedon ja antaa arvosanan.`);
|
||||||
const finalCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
const finalCode = Object.entries(files).map(([n,c]) => `--- ${n} ---\n${c}`).join('\n\n');
|
||||||
|
const fileList = Object.keys(files).join(', ');
|
||||||
const obsPrompt = (obs.prompt ? obs.prompt+'\n\n' : '') +
|
const obsPrompt = (obs.prompt ? obs.prompt+'\n\n' : '') +
|
||||||
`Write a project README.md report in markdown for: ${task}\n\n` +
|
`Write a project report in clean markdown for: ${task}\n\n` +
|
||||||
`IMPORTANT: Start the FIRST LINE with exactly one of these verdicts:\n` +
|
`FIRST LINE must be exactly one of:\n` +
|
||||||
`VERDICT: GREEN — project is production-ready, no issues\n` +
|
`VERDICT: GREEN\nVERDICT: ORANGE\nVERDICT: RED\n\n` +
|
||||||
`VERDICT: ORANGE — project works but has warnings or improvements needed\n` +
|
`Then write this report:\n\n` +
|
||||||
`VERDICT: RED — project has critical issues that must be fixed\n\n` +
|
`# ${task}\n\n` +
|
||||||
`Then include:\n` +
|
`## Overview\nOne paragraph describing what this project does.\n\n` +
|
||||||
`# Project: ${task}\n` +
|
`## Files\n| File | Purpose |\n|------|---------|` +
|
||||||
`## Files\n## How to run\n## API Endpoints\n## Architecture\n## Risk assessment\n\n` +
|
Object.entries(files).map(([n]) => `\n| ${n} | ... |`).join('') + `\n\n` +
|
||||||
|
`## Quick Start\n` +
|
||||||
|
'```bash\n' +
|
||||||
|
`git clone <repo>\ncd project\nuv sync\nuv run uvicorn main:app --reload\n` +
|
||||||
|
'```\n\n' +
|
||||||
|
`## Docker\n` +
|
||||||
|
'```bash\n' +
|
||||||
|
`docker build -t ${task.toLowerCase().replace(/[^a-z0-9]/g, '-')} .\ndocker run -p 8000:8000 ${task.toLowerCase().replace(/[^a-z0-9]/g, '-')}\n` +
|
||||||
|
'```\n\n' +
|
||||||
|
`## API Endpoints\n| Method | Path | Description |\n|--------|------|-------------|` +
|
||||||
|
`\n| POST | /items/ | Create |\n| GET | /items/ | List all |\n| GET | /items/{id} | Get by ID |\n| PUT | /items/{id} | Update |\n| DELETE | /items/{id} | Delete |\n` +
|
||||||
|
`(Adapt paths and descriptions to match the actual code)\n\n` +
|
||||||
|
`## Architecture\nDescribe the project structure and design decisions.\n\n` +
|
||||||
|
`## Risk Assessment\n| Severity | Issue |\n|----------|-------|\n| ... | ... |\n\n` +
|
||||||
`Project code:\n${finalCode}`;
|
`Project code:\n${finalCode}`;
|
||||||
const readme = await kpnRun(obs.model, obsPrompt);
|
const readme = await kpnRun(obs.model, obsPrompt);
|
||||||
if (readme) {
|
if (readme) {
|
||||||
|
|||||||
59
network-poc/install.sh
Executable file
59
network-poc/install.sh
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Kipinä Agentic Studio — asennusskripti (Debian/Ubuntu)
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== Kipinä Agentic Studio — Asennus ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Tarkistetaan käyttöjärjestelmä
|
||||||
|
if [ ! -f /etc/debian_version ]; then
|
||||||
|
echo "⚠ Tämä skripti on suunniteltu Debian/Ubuntu-järjestelmille."
|
||||||
|
echo " Muilla jakeluilla voit asentaa riippuvuudet manuaalisesti."
|
||||||
|
read -p " Jatketaanko? (k/e) " -n 1 -r; echo
|
||||||
|
[[ $REPLY =~ ^[Kk]$ ]] || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[1/6] Päivitetään pakettilistaus..."
|
||||||
|
sudo apt-get update -qq
|
||||||
|
|
||||||
|
echo "[2/6] Asennetaan peruspaketteja..."
|
||||||
|
sudo apt-get install -y -qq curl git build-essential pkg-config libssl-dev
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
if command -v rustc &>/dev/null; then
|
||||||
|
echo "[3/6] Rust löytyi: $(rustc --version)"
|
||||||
|
else
|
||||||
|
echo "[3/6] Asennetaan Rust..."
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||||
|
source "$HOME/.cargo/env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Node.js (Astro-frontend vaatii)
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
echo "[4/6] Node.js löytyi: $(node --version)"
|
||||||
|
else
|
||||||
|
echo "[4/6] Asennetaan Node.js 22..."
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||||
|
sudo apt-get install -y -qq nodejs
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ollama
|
||||||
|
if command -v ollama &>/dev/null; then
|
||||||
|
echo "[5/6] Ollama löytyi"
|
||||||
|
else
|
||||||
|
echo "[5/6] Asennetaan Ollama..."
|
||||||
|
curl -fsSL https://ollama.ai/install.sh | sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Malli
|
||||||
|
echo "[6/6] Ladataan kielimalli (qwen2.5-coder:3b)..."
|
||||||
|
ollama pull qwen2.5-coder:3b
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Asennus valmis! ==="
|
||||||
|
echo ""
|
||||||
|
echo "Käynnistä:"
|
||||||
|
echo " cd $(pwd)"
|
||||||
|
echo " ./network-poc/local.sh"
|
||||||
|
echo ""
|
||||||
|
echo "Avaa selaimessa: http://localhost:3000"
|
||||||
@@ -109,7 +109,7 @@ impl LlmEngine {
|
|||||||
.map_err(|e| format!("Ollama JSON: {}", e))?;
|
.map_err(|e| format!("Ollama JSON: {}", e))?;
|
||||||
|
|
||||||
let text = body["response"].as_str().unwrap_or("").to_string();
|
let text = body["response"].as_str().unwrap_or("").to_string();
|
||||||
let total_duration_ns = body["total_duration"].as_u64().unwrap_or(0);
|
let _total_duration_ns = body["total_duration"].as_u64().unwrap_or(0);
|
||||||
let eval_count = body["eval_count"].as_u64().unwrap_or(0) as usize;
|
let eval_count = body["eval_count"].as_u64().unwrap_or(0) as usize;
|
||||||
let eval_duration_ns = body["eval_duration"].as_u64().unwrap_or(1);
|
let eval_duration_ns = body["eval_duration"].as_u64().unwrap_or(1);
|
||||||
|
|
||||||
@@ -129,23 +129,17 @@ impl LlmEngine {
|
|||||||
|
|
||||||
/// Siivoa markdown-koodiblokki-merkit ja selitystekstit
|
/// Siivoa markdown-koodiblokki-merkit ja selitystekstit
|
||||||
fn strip_code_fences(text: &str) -> String {
|
fn strip_code_fences(text: &str) -> String {
|
||||||
let mut result = text.trim().to_string();
|
// Poistetaan kaikki ```-rivit ja kielitunnisteet (```python, ```rust jne.)
|
||||||
|
let lines: Vec<&str> = text.lines().collect();
|
||||||
// Poista aloittava ```lang
|
let filtered: Vec<&str> = lines.into_iter().filter(|line| {
|
||||||
if result.starts_with("```") {
|
let trimmed = line.trim();
|
||||||
if let Some(nl) = result.find('\n') {
|
// Poista rivit jotka ovat pelkkiä ``` tai ```kielitunniste
|
||||||
result = result[nl + 1..].to_string();
|
if trimmed.starts_with("```") {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
true
|
||||||
|
}).collect();
|
||||||
// Poista sulkeva ```
|
let mut result = filtered.join("\n").trim().to_string();
|
||||||
let trimmed = result.trim_end();
|
|
||||||
if trimmed.ends_with("```") {
|
|
||||||
let before = &trimmed[..trimmed.len() - 3];
|
|
||||||
if before.is_empty() || before.ends_with('\n') {
|
|
||||||
result = before.trim_end().to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Poista selitysteksti lopusta (kaikki rivin "\nPlease note" jälkeen jne.)
|
// Poista selitysteksti lopusta (kaikki rivin "\nPlease note" jälkeen jne.)
|
||||||
let lower = result.to_lowercase();
|
let lower = result.to_lowercase();
|
||||||
|
|||||||
Reference in New Issue
Block a user