10 Commits

Author SHA1 Message Date
Jaakko Vanhala
8468724a4c Architect-prompti parannettu, relaatiotuki templateihin, englanti-sääntö
- SPEC_SYSTEM: chain-of-thought, domain-esimerkit, anti-patternit, relaatiosäännöt
- Speksi-puhdistus: korjaa sa_type | None -virheet automaattisesti
- Etusivun teksti päivitetty
- Koodissa käytetään aina englantia (entity/field names)
2026-04-12 20:15:22 +03:00
Jaakko Vanhala
6ef71b7e5c Templates for different tasks 2026-04-12 20:02:25 +03:00
Jaakko Vanhala
b2ee8b9031 Pipelinen parannuksia building blockeilla 2026-04-12 18:48:14 +03:00
Jaakko Vanhala
c1a5f8aff5 ZIP-tiedostonimi lyhennetty max 3 sanaan 2026-04-12 16:07:46 +03:00
Jaakko Vanhala
8ee997cb56 Projektin ZIP-lataus projektikorttiin
Lataa .zip -nappi renderöidään projektikortin headeriin.
ZIP rakennetaan selaimessa ilman ulkoisia kirjastoja (CRC-32 + ZIP-rakenne inline).
Kansiorakenne säilyy: prompts/*.md -tiedostot menevät alihakemistoon.
2026-04-12 15:59:14 +03:00
Jaakko Vanhala
cd67562a67 QA katselmoi, DevOps keskittyy deploymenttiin
- Review-luuppi siirretty DevOps→QA: QA katselmoi koodin ja
  lähettää korjausvaatimukset Coderille (max 3 kierrosta)
- QA:n prompt laajennettu: review-checklist + testien kirjoitus
- DevOps:n prompt uusittu: Dockerfile + deployment -fokus
- Pipeline: Client→Manager→Coder→QA review↔Coder fix→QA testit→DevOps Dockerfile→Observer
- AGENTS_VERSION 4→5
2026-04-12 15:55:45 +03:00
Jaakko Vanhala
1f85c03624 Pipeline-rajoitteet kevennetty ja näkyville Asetukset-sivulle
- maxTokens: client/manager/devops/observer 512→1024
- Client: 200→400 sanaa, 3-5→3-8 ominaisuutta, MVP-rajoitus poistettu
- Manager: 4-5→8 tiedostoa, vapaa tila 6→8
- Terminaali: 100→300 riviä, CrewAI prompt truncation 20→50 riviä
- Uusi pipelineConfig-objekti (localStorage-persistenssi)
- Asetukset-sivulle Pipeline-rajoitteet -osio sliderien kanssa
- AGENTS_VERSION 3→4
2026-04-12 15:47:46 +03:00
Jaakko Vanhala
74a2045def Landing page + oppimispolku + esimerkkiprojektit
1) Landing: gecko hero, projektin syöttökenttä, "Käynnistä"-nappi
2) Oppimispolku-välilehti: promptLog step-by-step (system prompt, syöte, tulos)
3) Kolme esimerkkiprojektia: Käyttäjähallinta-API, UWB-data-analyysi, Todo-sovellus
4) Landing → App -siirtymä käynnistää pipelinen suoraan
2026-04-12 15:24:44 +03:00
Jaakko Vanhala
9b2b7767b5 Depoa paranneltu 2026-04-12 14:28:58 +03:00
Jaakko Vanhala
1718805978 CrewAI-yhteensopiva projektioutput: agents.yaml, tasks.yaml, crew.py, prompts/
Pipeline kerää promptLog-listan jokaisesta agenttikutsusta (system prompt +
syöte + tulos) ja generoi lopuksi CrewAI-rakenteen files-objektiin.
Korjattu myös template.order.length-kaatuminen vapaassa tilassa.
2026-04-12 13:41:04 +03:00
12 changed files with 1192 additions and 282 deletions

5
.gitignore vendored
View File

@@ -42,4 +42,7 @@ Cargo.lock
*.log *.log
# Wanha versio # Wanha versio
temp/ temp/
# Muut
zipit/**

View File

@@ -230,6 +230,144 @@ mitä luokkia importata.
--- ---
## Rakennuspalaset vs. vapaa generointi
Kielimalli voi generoida koodia kahdella perustavanlaatuisesti eri tavalla.
Ymmärtäminen milloin kumpikin toimii on avain luotettavaan koodigenerointi-pipelineen.
### Tapa 1: Vapaa generointi (naivi)
LLM generoi jokaisen tiedoston tyhjästä. Prompti kuvaa mitä halutaan,
malli tuottaa koko tiedoston — importeista lähtien.
```mermaid
flowchart LR
P["Prompti"] --> LLM1["LLM: models.py"]
LLM1 --> V1{"Validointi"}
V1 -->|virhe| LLM1
V1 -->|ok| LLM2["LLM: schemas.py"]
LLM2 --> V2{"Validointi"}
V2 -->|virhe| LLM2
V2 -->|ok| LLM3["LLM: main.py"]
LLM3 --> V3{"..."}
style V1 fill:#1a1e2e,stroke:#f85149,color:#c9d1d9
style V2 fill:#1a1e2e,stroke:#f85149,color:#c9d1d9
style V3 fill:#1a1e2e,stroke:#f85149,color:#c9d1d9
```
**Ongelma:** Pieni malli (0.5B7B) tekee toistuvia rakenteellisia virheitä:
| Virhe | Esiintymistiheys | Selitys |
|-------|:---:|------|
| Puuttuva import | ~60% | `from datetime import date` unohtuu |
| SQLite `connect_args` | ~80% | Malli ei muista SQLite-erityisyyttä |
| Väärä Enum-käyttö | ~50% | Sekoittaa `sqlalchemy.Enum` ja `enum.Enum` |
| Poetry pyproject.toml:ssa | ~40% | Malli suosii Poetryä vaikka ohje sanoo uv |
| Testit kopioivat koko appin | ~70% | Malli ei osaa importata, luo uudet reitit |
Retry-loopilla (virhe → uusi yritys virheviestin kanssa) osa korjautuu,
mutta **sama malli toistaa samoja virheitä** koska ne johtuvat harjoitusdatasta.
7 tiedoston projekti vaatii 714 LLM-kutsua ja 80120 sekuntia.
### Tapa 2: Rakennuspalaset (template pipeline)
LLM:ltä pyydetään **vain JSON-speksi** — entiteetit, kentät ja tyypit.
Koodi kootaan mekaanisesti valmiista pohjista joiden rakenne on todistettavasti
oikein.
```mermaid
flowchart LR
P["Projektin kuvaus"] --> LLM["LLM: JSON-speksi"]
LLM --> S["{ entities: [...] }"]
S --> T1["Template: models.py"]
S --> T2["Template: schemas.py"]
S --> T3["Template: main.py"]
S --> T4["Template: test_main.py"]
S --> T5["Template: Dockerfile"]
T1 & T2 & T3 & T4 & T5 --> D["Docker build + pytest"]
style LLM fill:#1a1e2e,stroke:#d29922,color:#c9d1d9
style S fill:#1a1e2e,stroke:#3fb950,color:#c9d1d9
style D fill:#1a1e2e,stroke:#58a6ff,color:#c9d1d9
```
**Idea:** Malli on hyvä päättämään *mitä* (entiteetit, kentät), mutta huono
muistamaan *miten* (importit, engine setup, testikonfiguraatio). Annetaan
mallin tehdä se missä se on hyvä, ja hoidetaan loput mekaanisesti.
### LLM:n ainoa tehtävä
Malli tuottaa JSON-rakenteen kuten:
```json
{
"project_name": "todo-app",
"entities": [
{
"name": "Todo",
"table_name": "todos",
"fields": [
{"name": "title", "sa_type": "String(255)", "py_type": "str", "nullable": false},
{"name": "due_date", "sa_type": "Date", "py_type": "date | None", "nullable": true},
{"name": "status", "sa_type": "String(20)", "py_type": "str", "default": "pending"}
]
}
],
"extra_imports": ["from datetime import date"]
}
```
Tämä on yksinkertainen tehtävä jossa pienikin malli onnistuu luotettavasti:
entiteettien tunnistus projektin kuvauksesta ja kenttätyyppien valinta.
### Template täyttää loput
Jokainen template on kuin madlib — aukot täytetään speksin datalla:
**models.py template (yksinkertaistettu):**
```python
from sqlalchemy import create_engine, Column, Integer, {sa_types}
# ... aina samat importit, engine setup, SessionLocal ...
class {entity.name}(Base):
__tablename__ = "{entity.table_name}"
id = Column(Integer, primary_key=True, index=True)
{field.name} = Column({field.sa_type}, nullable={field.nullable})
# ... jokainen kenttä speksistä ...
```
Tulos: importit ovat aina oikein, `connect_args` on aina mukana,
testit importoivat aina `main.py`:stä eivätkä kopioi sitä.
### Vertailu: mittaustulokset
| | Vapaa generointi | Rakennuspalaset |
|---|:---:|:---:|
| LLM-kutsuja | 714 | **1** |
| Aika | 80120s | **~20s** |
| Syntaksi OK | ~70% | **100%** |
| Docker build | vaihteleva | **100%** |
| Pytest läpi | 0% | **100%** |
| API toimii | ~30% | **100%** |
### Milloin kumpikin toimii
**Rakennuspalaset** kun:
- Projektin rakenne on tunnettu (FastAPI + SQLAlchemy CRUD)
- Laatu ja luotettavuus ovat tärkeitä
- Malli on pieni (0.5B7B)
**Vapaa generointi** kun:
- Projektin rakenne on epätavallinen
- Tarvitaan custom-logiikkaa jota template ei kata
- Malli on riittävän iso (>70B tai pilvi-API)
Paras lopputulos syntyy yhdistelmällä: **rakennuspalaset perusrakenteelle,
vapaa generointi business-logiikalle**.
---
## Laadun parantaminen ## Laadun parantaminen
### 1. Isompi malli (suurin vaikutus) ### 1. Isompi malli (suurin vaikutus)

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -1,37 +1,37 @@
<!-- Agenttigalleria + konfigurointipaneeli --> <!-- Agent gallery + configuration panel -->
<div style="display:flex;gap:16px;padding:10px 0;align-items:flex-start"> <div style="display:flex;gap:16px;padding:10px 0;align-items:flex-start">
<!-- Agenttilista (drag & drop) --> <!-- Agenttilista (drag & drop) -->
<div id="agent-bar" style="display:flex;gap:6px;align-items:flex-end;flex-wrap:wrap"> <div id="agent-bar" style="display:flex;gap:6px;align-items:flex-end;flex-wrap:wrap">
<!-- Renderöidään JS:stä --> <!-- Renderöidään JS:stä -->
</div> </div>
<!-- + Lisää agentti --> <!-- + Add agent -->
<div id="add-agent-btn" class="agent-avatar" onclick="addCustomAgent()" title="Lisää oma agentti" style="opacity:0.4"> <div id="add-agent-btn" class="agent-avatar" onclick="addCustomAgent()" title="Add custom agent" style="opacity:0.4">
<div style="width:48px;height:48px;border-radius:50%;border:2px dashed var(--border);display:flex;align-items:center;justify-content:center;font-size:24px;color:var(--border)">+</div> <div style="width:48px;height:48px;border-radius:50%;border:2px dashed var(--border);display:flex;align-items:center;justify-content:center;font-size:24px;color:var(--border)">+</div>
<span style="font-size:10px;color:#8b949e;text-align:center;display:block">Lisää</span> <span style="font-size:10px;color:#8b949e;text-align:center;display:block">Add</span>
</div> </div>
</div> </div>
<!-- Agentin konfigurointipaneeli (avautuu klikkaamalla avataria) --> <!-- Agent configuration panel (opens clicking avatar) -->
<div id="agent-config" style="display:none;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:16px;margin-bottom:10px"> <div id="agent-config" style="display:none;background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:16px;margin-bottom:10px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<div style="display:flex;align-items:center;gap:10px"> <div style="display:flex;align-items:center;gap:10px">
<img id="config-avatar" src="" style="width:40px;height:40px;border-radius:50%"> <img id="config-avatar" src="" style="width:40px;height:40px;border-radius:50%">
<div> <div>
<input id="config-name" style="background:transparent;border:none;color:var(--text);font-size:16px;font-weight:600;outline:none;width:200px" placeholder="Agentin nimi"> <input id="config-name" style="background:transparent;border:none;color:var(--text);font-size:16px;font-weight:600;outline:none;width:200px" placeholder="Agent Name">
<div id="config-role" style="font-size:11px;color:#8b949e"></div> <div id="config-role" style="font-size:11px;color:#8b949e"></div>
</div> </div>
</div> </div>
<div style="display:flex;gap:6px"> <div style="display:flex;gap:6px">
<button class="btn btn-red" onclick="deleteAgent()" title="Poista agentti">Poista</button> <button class="btn btn-red" onclick="deleteAgent()" title="Delete agent">Delete</button>
<button class="btn btn-muted" onclick="closeAgentConfig()">Sulje</button> <button class="btn btn-muted" onclick="closeAgentConfig()">Close</button>
</div> </div>
</div> </div>
<!-- Malli --> <!-- Model -->
<div style="margin-bottom:10px"> <div style="margin-bottom:10px">
<label style="font-size:12px;color:#8b949e;display:block;margin-bottom:4px">Kielimalli</label> <label style="font-size:12px;color:#8b949e;display:block;margin-bottom:4px">Model</label>
<select id="config-model" style="background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:6px 10px;font-size:13px;width:100%"> <select id="config-model" style="background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:6px 10px;font-size:13px;width:100%">
<option value="qwen-coder">Qwen2.5-Coder:0.5B (selain)</option> <option value="qwen-coder">Qwen2.5-Coder:0.5B (browser)</option>
<option value="qwen-coder-3b">Qwen2.5-Coder:3B (Ollama)</option> <option value="qwen-coder-3b">Qwen2.5-Coder:3B (Ollama)</option>
<option value="qwen2.5-coder:7b">Qwen2.5-Coder:7B (Ollama)</option> <option value="qwen2.5-coder:7b">Qwen2.5-Coder:7B (Ollama)</option>
<option value="qwen2.5-coder:1.5b">Qwen2.5-Coder:1.5B (Ollama)</option> <option value="qwen2.5-coder:1.5b">Qwen2.5-Coder:1.5B (Ollama)</option>
@@ -39,41 +39,41 @@
</div> </div>
<!-- System prompt --> <!-- System prompt -->
<div style="margin-bottom:10px" title="Agentin perusohje joka lähetetään kielimallille jokaisessa pyynnössä.&#10;&#10;Hyvän promptin rakenne:&#10;1. Rooli: 'You are an expert...'&#10;2. Säännöt: RULES/CRITICAL RULES listana&#10;3. Esimerkit: EXAMPLE OUTPUT&#10;4. Kiellot: NEVER-lista&#10;&#10;Vinkki: käytä englantia — malli ymmärtää sen paremmin ja se kuluttaa vähemmän tokeneita."> <div style="margin-bottom:10px" title="System prompt sent to the LLM on every request.&#10;&#10;Good prompt structure:&#10;1. Role: 'You are an expert...'&#10;2. Rules: RULES/CRITICAL RULES as list&#10;3. Examples: EXAMPLE OUTPUT&#10;4. Restrictions: NEVER-list&#10;">
<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" 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> <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="Describe the agent's role and behavior..."></textarea>
</div> </div>
<!-- Sampling-parametrit --> <!-- Sampling Parameters -->
<div style="margin-bottom:10px"> <div style="margin-bottom:10px">
<label style="font-size:12px;color:#8b949e;display:block;margin-bottom:8px">Sampling-parametrit</label> <label style="font-size:12px;color:#8b949e;display:block;margin-bottom:8px">Sampling Parameters</label>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div title="Kontrolloi 'luovuutta'. Matala arvo (0.2-0.4) tuottaa ennustettavaa, toistettavaa koodia — hyvä testaajille ja reviewereille. Keskiarvo (0.6-0.8) on paras koodin generointiin. Korkea arvo (1.0+) lisää vaihtelua mutta myös virheitä.&#10;&#10;Suositus:&#10;• Manageri: 0.5 (tarkat tiedostolistat)&#10;• Koodari: 0.7 (toimiva koodi + vaihtelu)&#10;• Testaaja: 0.3 (deterministinen arviointi)"> <div title="Controls 'creativity'. Low value (0.2-0.4) produces predictable, repeatable code — good for testers and reviewers. Medium value (0.6-0.8) is best for generating code. High value (1.0+) adds variation but also errors.&#10;&#10;Recommendation:&#10;• Manager: 0.5 (precise file lists)&#10;• Coder: 0.7 (working code + variation)&#10;• Tester: 0.3 (deterministic evaluation)">
<label style="font-size:11px;color:#8b949e;cursor:help">Temperature 💡 <span id="config-temp-val" style="color:var(--accent);float:right">0.7</span></label> <label style="font-size:11px;color:#8b949e;cursor:help">Temperature 💡 <span id="config-temp-val" style="color:var(--accent);float:right">0.7</span></label>
<input type="range" id="config-temperature" min="0" max="1.5" step="0.1" value="0.7" style="width:100%;accent-color:var(--accent)"> <input type="range" id="config-temperature" min="0" max="1.5" step="0.1" value="0.7" style="width:100%;accent-color:var(--accent)">
<div style="font-size:10px;color:#30363d">0=tarkka · 0.7=oletus · 1.5=luova</div> <div style="font-size:10px;color:#30363d">0=strict · 0.7=default · 1.5=creative</div>
</div> </div>
<div title="Vastauksen maksimipituus tokeneina (~1 token ≈ 4 merkkiä).&#10;&#10;Suositus:&#10;• Manageri: 256-512 (lyhyet tiedostolistat)&#10;• Koodari: 1024-2048 (täydet tiedostot, CRUD-endpointit)&#10;• Testaaja: 256-512 (lyhyet arvioinnit)&#10;&#10;Jos koodi katkeaa kesken, nosta tätä. Jos malli tuottaa turhaa toistoa, laske."> <div title="Maximum response length in tokens (~1 token ≈ 4 chars).&#10;&#10;Recommendation:&#10;• Manager: 256-512 (short lists)&#10;• Coder: 1024-2048 (full files, CRUD endpoints)&#10;• Tester: 256-512 (short evaluations)&#10;&#10;If code cuts off early, increase this.">
<label style="font-size:11px;color:#8b949e;cursor:help">Max tokens 💡 <span id="config-maxtok-val" style="color:var(--accent);float:right">1024</span></label> <label style="font-size:11px;color:#8b949e;cursor:help">Max tokens 💡 <span id="config-maxtok-val" style="color:var(--accent);float:right">1024</span></label>
<input type="range" id="config-maxtokens" min="64" max="4096" step="64" value="1024" style="width:100%;accent-color:var(--accent)"> <input type="range" id="config-maxtokens" min="64" max="4096" step="64" value="1024" style="width:100%;accent-color:var(--accent)">
<div style="font-size:10px;color:#30363d">Vastauksen maksimipituus</div> <div style="font-size:10px;color:#30363d">Maximum response length</div>
</div> </div>
<div title="Montako todennäköisintä tokenia huomioidaan valinnassa. Pieni arvo (1-10) tekee vastauksesta deterministisen. Suuri arvo (50-100) sallii harvinaisempia sanoja.&#10;&#10;Suositus:&#10;• Boilerplate-koodi: 20-30 (tutut patternit)&#10;• Yleiskoodi: 40 (hyvä oletus)&#10;• Luova teksti: 60-80&#10;&#10;Yleensä ei tarvitse muuttaa oletuksesta."> <div title="How many most probable tokens are considered. Low value (1-10) makes response deterministic. High value (50-100) allows rarer words.&#10;&#10;Recommendation:&#10;• Boilerplate code: 20-30 (familiar patterns)&#10;• General code: 40 (good default)&#10;• Creative text: 60-80">
<label style="font-size:11px;color:#8b949e;cursor:help">Top-K 💡 <span id="config-topk-val" style="color:var(--accent);float:right">40</span></label> <label style="font-size:11px;color:#8b949e;cursor:help">Top-K 💡 <span id="config-topk-val" style="color:var(--accent);float:right">40</span></label>
<input type="range" id="config-topk" min="1" max="100" step="1" value="40" style="width:100%;accent-color:var(--accent)"> <input type="range" id="config-topk" min="1" max="100" step="1" value="40" style="width:100%;accent-color:var(--accent)">
<div style="font-size:10px;color:#30363d">1=greedy · 40=oletus · 100=laaja</div> <div style="font-size:10px;color:#30363d">1=greedy · 40=default · 100=wide</div>
</div> </div>
<div title="Vähentää jo tuotettujen sanojen todennäköisyyttä. Estää mallia toistamasta samaa lausetta. Liian korkea arvo (>1.5) voi rikkoa koodin koska samat avainsanat (return, if, def) ovat tarpeellisia.&#10;&#10;Suositus:&#10;• Koodi: 1.1-1.2 (lievä, sallii toiston)&#10;• Teksti: 1.15-1.3 (vahvempi)&#10;• Review: 1.0-1.1 (ei rangaistusta, lyhyet vastaukset)"> <div title="Reduces the probability of already generated words. Prevents model from repeating same sentences. Too high value (>1.5) can break code because common keywords (return, if, def) are necessary.&#10;&#10;Recommendation:&#10;• Code: 1.1-1.2 (mild, allows repetition)&#10;• Text: 1.15-1.3 (stronger penalty)&#10;• Review: 1.0-1.1 (no penalty, short answers)">
<label style="font-size:11px;color:#8b949e;cursor:help">Repetition penalty 💡 <span id="config-rep-val" style="color:var(--accent);float:right">1.15</span></label> <label style="font-size:11px;color:#8b949e;cursor:help">Repetition penalty 💡 <span id="config-rep-val" style="color:var(--accent);float:right">1.15</span></label>
<input type="range" id="config-repeat" min="1.0" max="2.0" step="0.05" value="1.15" style="width:100%;accent-color:var(--accent)"> <input type="range" id="config-repeat" min="1.0" max="2.0" step="0.05" value="1.15" style="width:100%;accent-color:var(--accent)">
<div style="font-size:10px;color:#30363d">1.0=ei · 1.15=oletus · 2.0=vahva</div> <div style="font-size:10px;color:#30363d">1.0=none · 1.15=default · 2.0=strong</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Pipeline-järjestys --> <!-- Pipeline order -->
<div> <div>
<label style="font-size:12px;color:#8b949e;display:block;margin-bottom:4px">Pipeline-järjestys <span style="color:var(--border)">(vedä järjestääksesi)</span></label> <label style="font-size:12px;color:#8b949e;display:block;margin-bottom:4px">Pipeline Order <span style="color:var(--border)">(drag to sort)</span></label>
<div id="config-pipeline" style="display:flex;gap:4px;flex-wrap:wrap"></div> <div id="config-pipeline" style="display:flex;gap:4px;flex-wrap:wrap"></div>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
<!-- Monaco Editor paneeli --> <!-- Monaco Editor paneeli -->
<div id="panel-editor" class="panel"> <div id="panel-editor" class="panel">
<div style="display:flex;height:calc(100vh - 200px);gap:0;border:1px solid var(--border);border-radius:6px;overflow:hidden"> <div style="display:flex;flex:1;min-height:0;gap:0;border:1px solid var(--border);border-radius:6px;overflow:hidden">
<div id="editor-filetree" style="width:200px;min-width:150px;background:var(--bg);border-right:1px solid var(--border);overflow-y:auto;font-family:'Courier New',monospace;font-size:13px"> <div id="editor-filetree" style="width:200px;min-width:150px;background:var(--bg);border-right:1px solid var(--border);overflow:auto;resize:horizontal;font-family:'Courier New',monospace;font-size:13px">
<div style="padding:10px 12px;color:#8b949e;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid var(--border)">Tiedostot</div> <div style="padding:10px 12px;color:#8b949e;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;border-bottom:1px solid var(--border)">Tiedostot</div>
<div id="editor-file-list" style="padding:4px 0"> <div id="editor-file-list" style="padding:4px 0">
<div style="padding:8px 16px;color:#8b949e;font-size:12px">Generoi projekti:<br><code style="color:var(--accent)">kpn project "..."</code></div> <div style="padding:8px 16px;color:#8b949e;font-size:12px">Generoi projekti:<br><code style="color:var(--accent)">kpn project "..."</code></div>

View File

@@ -58,6 +58,49 @@
</select> </select>
</div> </div>
<!-- Pipeline-rajoitteet -->
<div class="settings-section">
<h3 class="settings-title">Pipeline-rajoitteet</h3>
<p class="settings-desc">Projektin generoinnin rajat. Suuremmat arvot = rikkaampi output, hitaampi suoritus.</p>
<div class="settings-grid">
<div>
<label class="settings-label">Client: max sanat <span id="set-plc-words-val" class="settings-val">400</span></label>
<input type="range" id="set-plc-words" min="100" max="800" step="50" value="400" class="settings-slider">
<div class="settings-hint">Vaatimusmäärittelyn maksimipituus sanoina</div>
</div>
<div>
<label class="settings-label">Client: max ominaisuudet <span id="set-plc-feats-val" class="settings-val">8</span></label>
<input type="range" id="set-plc-feats" min="3" max="15" step="1" value="8" class="settings-slider">
<div class="settings-hint">Montako ominaisuutta vaatimuksiin</div>
</div>
<div>
<label class="settings-label">Manager: max tiedostot <span id="set-plc-mfiles-val" class="settings-val">8</span></label>
<input type="range" id="set-plc-mfiles" min="3" max="15" step="1" value="8" class="settings-slider">
<div class="settings-hint">Managerin suunnittelemien tiedostojen yläraja</div>
</div>
<div>
<label class="settings-label">Vapaa tila: max tiedostot <span id="set-plc-ffiles-val" class="settings-val">8</span></label>
<input type="range" id="set-plc-ffiles" min="3" max="15" step="1" value="8" class="settings-slider">
<div class="settings-hint">Tiedostoraja kun ei mallipohjaa</div>
</div>
<div>
<label class="settings-label">Review-kierrokset <span id="set-plc-review-val" class="settings-val">3</span></label>
<input type="range" id="set-plc-review" min="1" max="5" step="1" value="3" class="settings-slider">
<div class="settings-hint">Katselmointi-korjaus-syklien max määrä</div>
</div>
<div>
<label class="settings-label">Terminaali: max rivit <span id="set-plc-term-val" class="settings-val">300</span></label>
<input type="range" id="set-plc-term" min="50" max="1000" step="50" value="300" class="settings-slider">
<div class="settings-hint">Terminaalin näyttämien rivien yläraja</div>
</div>
<div>
<label class="settings-label">CrewAI: prompt-rivit <span id="set-plc-crew-val" class="settings-val">50</span></label>
<input type="range" id="set-plc-crew" min="10" max="200" step="10" value="50" class="settings-slider">
<div class="settings-hint">tasks.yaml:n promptin max rivimäärä</div>
</div>
</div>
</div>
<!-- Reset --> <!-- Reset -->
<div style="margin-top:24px;padding-top:16px;border-top:1px solid var(--border)"> <div style="margin-top:24px;padding-top:16px;border-top:1px solid var(--border)">
<button class="btn btn-red" onclick="resetSettings()" style="padding:6px 16px">Palauta oletukset</button> <button class="btn btn-red" onclick="resetSettings()" style="padding:6px 16px">Palauta oletukset</button>

File diff suppressed because it is too large Load Diff

View File

@@ -20,10 +20,24 @@ body {
min-height: 100vh; min-height: 100vh;
} }
.container { max-width: 1600px; margin: 0 auto; padding: 20px 40px; } .container {
max-width: 1600px;
margin: 0 auto;
padding: 20px 40px;
}
#app.container {
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
#app:not(.active) { display: none; }
#landing.hidden { display: none; }
/* Tabs */ /* Tabs */
.tabs { display: flex; gap: 4px; margin-bottom: 16px; } .tabs { display: flex; gap: 4px; margin-bottom: 16px; flex-shrink: 0; }
.tab { .tab {
padding: 10px 20px; border-radius: 6px 6px 0 0; cursor: pointer; padding: 10px 20px; border-radius: 6px 6px 0 0; cursor: pointer;
border: 1px solid var(--border); border-bottom: none; border: 1px solid var(--border); border-bottom: none;
@@ -33,7 +47,7 @@ body {
/* Panels */ /* Panels */
.panel { display: none; } .panel { display: none; }
.panel.active { display: block; } .panel.active { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto; }
/* Status bar */ /* Status bar */
.status-bar { .status-bar {
@@ -52,7 +66,7 @@ body {
.terminal { .terminal {
background: #010409; border: 1px solid var(--border); border-top: none; background: #010409; border: 1px solid var(--border); border-top: none;
font-family: 'Courier New', monospace; font-size: 16px; font-family: 'Courier New', monospace; font-size: 16px;
min-height: 400px; max-height: 70vh; overflow-y: auto; flex: 1; min-height: 0; max-height: none; overflow-y: auto;
padding: 12px 16px; padding: 12px 16px;
} }
.terminal-line { padding: 1px 0; white-space: pre-wrap; word-break: break-word; } .terminal-line { padding: 1px 0; white-space: pre-wrap; word-break: break-word; }
@@ -81,6 +95,12 @@ body {
} }
.dd-item:hover, .dd-item.active { background: var(--border); color: var(--accent); } .dd-item:hover, .dd-item.active { background: var(--border); color: var(--accent); }
#editor-file-list .dd-item {
white-space: pre-wrap;
word-break: break-all;
line-height: 1.4;
}
/* Pipeline progress */ /* Pipeline progress */
.pipeline-bar { .pipeline-bar {
display: none; padding: 8px 14px; background: var(--bg); display: none; padding: 8px 14px; background: var(--bg);
@@ -102,6 +122,7 @@ body {
.project-tab { .project-tab {
padding: 4px 10px; cursor: pointer; border-radius: 4px 4px 0 0; padding: 4px 10px; cursor: pointer; border-radius: 4px 4px 0 0;
font-size: 12px; color: #8b949e; font-size: 12px; color: #8b949e;
white-space: nowrap; flex-shrink: 0;
} }
.project-tab.active { background: var(--panel); color: var(--accent); border: 1px solid var(--border); border-bottom: none; } .project-tab.active { background: var(--panel); color: var(--accent); border: 1px solid var(--border); border-bottom: none; }
@@ -165,6 +186,13 @@ body {
.agent-avatar.active img { .agent-avatar.active img {
border-color: var(--accent); border-color: var(--accent);
box-shadow: 0 0 25px rgba(88,166,255,0.8); box-shadow: 0 0 25px rgba(88,166,255,0.8);
animation: agentBlink 1.5s infinite;
}
@keyframes agentBlink {
0% { opacity: 0.8; box-shadow: 0 0 15px rgba(88,166,255,0.5); }
50% { opacity: 1.0; box-shadow: 0 0 35px rgba(88,166,255,1.0); }
100% { opacity: 0.8; box-shadow: 0 0 15px rgba(88,166,255,0.5); }
} }
/* Settings */ /* Settings */
@@ -195,6 +223,207 @@ body {
display: grid; grid-template-columns: 1fr 1fr; gap: 16px; display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
} }
/* ===== LANDING PAGE ===== */
#landing {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
min-height: 100vh;
position: relative;
overflow: hidden;
}
.bg-mesh {
position: fixed; inset: 0; z-index: -1;
background:
radial-gradient(ellipse 80% 60% at 20% 40%, rgba(255,107,0,0.08) 0%, transparent 70%),
radial-gradient(ellipse 60% 50% at 80% 20%, rgba(88,166,255,0.06) 0%, transparent 70%),
var(--bg);
}
.landing-nav {
padding: 20px 40px;
}
.landing-logo { text-decoration: none; font-size: 18px; font-weight: 700; }
.logo-accent { color: #ff6b00; }
.logo-sub { color: #8b949e; font-weight: 400; }
/* Hero */
.hero {
padding: 60px 40px 40px;
}
.hero-container {
max-width: 1200px; margin: 0 auto;
display: grid; grid-template-columns: 1fr 400px; gap: 60px; align-items: center;
}
.hero-title {
font-size: clamp(2rem, 4vw, 3rem); font-weight: 800;
line-height: 1.15; color: #e6edf3; margin-bottom: 16px;
}
.hero-divider {
width: 60px; height: 3px; background: #ff6b00;
border-radius: 2px; margin-bottom: 20px;
}
.hero-desc {
font-size: 1.05rem; color: #8b949e; line-height: 1.7; margin-bottom: 12px;
}
.hero-notice {
font-size: 0.9rem; color: #6e7681; line-height: 1.6;
border-left: 2px solid var(--border); padding-left: 12px; margin-bottom: 28px;
}
/* Hero input */
.hero-input-group {
display: flex; gap: 8px; margin-bottom: 20px;
}
.hero-input {
flex: 1; padding: 14px 18px; font-size: 16px;
font-family: 'JetBrains Mono', 'Courier New', monospace;
background: var(--panel); color: var(--text);
border: 1px solid var(--border); border-radius: 8px;
outline: none; transition: border-color 0.2s;
}
.hero-input:focus {
border-color: #ff6b00; box-shadow: 0 0 0 3px rgba(255,107,0,0.15);
}
.hero-input::placeholder { color: #484f58; }
.hero-input.shake {
animation: shake 0.4s ease;
border-color: #f85149;
box-shadow: 0 0 0 3px rgba(248,81,73,0.2);
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
20%, 60% { transform: translateX(-6px); }
40%, 80% { transform: translateX(6px); }
}
.hero-btn {
padding: 14px 28px; font-size: 16px; font-weight: 600;
font-family: 'Inter', sans-serif;
background: #ff6b00; color: #fff; border: none; border-radius: 8px;
cursor: pointer; transition: background 0.2s, transform 0.1s;
white-space: nowrap;
}
.hero-btn:hover { background: #e05e00; transform: translateY(-1px); }
.hero-btn:active { transform: translateY(0); }
/* Example buttons */
.hero-examples { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.hero-examples-label { color: #6e7681; font-size: 14px; margin-right: 4px; }
.example-btn {
padding: 8px 16px; font-size: 13px; font-family: 'Inter', sans-serif;
background: transparent; color: var(--accent);
border: 1px solid var(--border); border-radius: 6px;
cursor: pointer; transition: all 0.2s;
}
.example-btn:hover {
border-color: var(--accent); background: rgba(88,166,255,0.08);
}
/* Hero orb */
.hero-orb-wrapper {
display: flex; justify-content: center; align-items: center;
}
.hero-orb {
width: 340px; height: 340px; border-radius: 50%;
background: radial-gradient(circle at 30% 30%, rgba(255,107,0,0.15) 0%, transparent 70%);
display: flex; align-items: center; justify-content: center;
animation: orb-float 6s ease-in-out infinite;
}
.hero-orb-img {
width: 100%; height: 100%; object-fit: contain;
filter: drop-shadow(0 0 40px rgba(255,107,0,0.25));
}
@keyframes orb-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-12px); }
}
/* How section */
.how-section {
padding: 60px 40px;
background: rgba(22,27,34,0.6);
border-top: 1px solid var(--border);
}
.how-container { max-width: 900px; margin: 0 auto; }
.how-title {
text-align: center; font-size: 1.5rem; font-weight: 700;
color: #e6edf3; margin-bottom: 40px;
}
.how-steps {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px;
}
.how-step {
text-align: center; padding: 24px;
background: var(--panel); border: 1px solid var(--border);
border-radius: 12px; transition: border-color 0.3s;
}
.how-step:hover { border-color: rgba(255,107,0,0.4); }
.how-step-num {
width: 40px; height: 40px; line-height: 40px;
border-radius: 50%; background: rgba(255,107,0,0.12);
color: #ff6b00; font-weight: 700; font-size: 18px;
margin: 0 auto 14px;
}
.how-step h3 { color: #e6edf3; font-size: 1rem; margin-bottom: 8px; }
.how-step p { color: #8b949e; font-size: 0.9rem; line-height: 1.5; }
/* Landing footer */
.landing-footer {
text-align: center; padding: 32px 40px;
color: #484f58; font-size: 13px;
border-top: 1px solid var(--border);
}
.landing-footer a { color: #8b949e; }
/* Responsive */
@media (max-width: 860px) {
.hero-container { grid-template-columns: 1fr; gap: 32px; }
.hero-orb-wrapper { order: -1; }
.hero-orb { width: 220px; height: 220px; }
.how-steps { grid-template-columns: 1fr; }
.hero-input-group { flex-direction: column; }
}
/* ===== OPPIMISPOLKU ===== */
.learn-step {
margin: 12px 0; border: 1px solid var(--border);
border-radius: 8px; background: var(--panel); overflow: hidden;
}
.learn-step-header {
display: flex; align-items: center; gap: 12px;
padding: 12px 16px; cursor: pointer;
transition: background 0.15s;
}
.learn-step-header:hover { background: rgba(88,166,255,0.04); }
.learn-step-num {
width: 28px; height: 28px; line-height: 28px; text-align: center;
border-radius: 50%; background: rgba(255,107,0,0.12);
color: #ff6b00; font-weight: 700; font-size: 13px; flex-shrink: 0;
}
.learn-step-agent {
font-weight: 600; color: #e6edf3; font-size: 14px;
}
.learn-step-label {
color: #8b949e; font-size: 13px; margin-left: auto;
}
.learn-step-body {
display: none; padding: 0 16px 16px;
border-top: 1px solid var(--border);
}
.learn-step-body.open { display: block; }
.learn-section-title {
color: var(--accent); font-size: 12px; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.5px;
margin: 14px 0 6px;
}
.learn-code {
font-family: 'JetBrains Mono', 'Courier New', monospace;
font-size: 12px; line-height: 1.6;
background: #010409; border: 1px solid var(--border);
border-radius: 6px; padding: 12px; overflow-x: auto;
max-height: 300px; overflow-y: auto; white-space: pre-wrap;
}
/* Animations */ /* Animations */
@keyframes blink { 0%,100% { opacity:1 } 50% { opacity:0 } } @keyframes blink { 0%,100% { opacity:1 } 50% { opacity:0 } }
@keyframes spin { to { transform: rotate(360deg) } } @keyframes spin { to { transform: rotate(360deg) } }

View File

@@ -1011,15 +1011,15 @@ async fn handle_socket(socket: WebSocket, state: Arc<AppState>, ip: IpAddr) {
if let Some(obj) = json.as_object_mut() { if let Some(obj) = json.as_object_mut() {
let model = obj.get("model").and_then(|v| v.as_str()).unwrap_or("?"); let model = obj.get("model").and_then(|v| v.as_str()).unwrap_or("?");
let prompt = obj.get("prompt").and_then(|v| v.as_str()).unwrap_or(""); let prompt = obj.get("prompt").and_then(|v| v.as_str()).unwrap_or("");
let response = obj.get("response").and_then(|v| v.as_str()).unwrap_or(""); let _response = obj.get("response").and_then(|v| v.as_str()).unwrap_or("");
let tok_gen = obj.get("tokens_generated").and_then(|v| v.as_u64()).unwrap_or(0); let tok_gen = obj.get("tokens_generated").and_then(|v| v.as_u64()).unwrap_or(0);
let duration = obj.get("duration_ms").and_then(|v| v.as_f64()).unwrap_or(0.0); let duration = obj.get("duration_ms").and_then(|v| v.as_f64()).unwrap_or(0.0);
let tok_s = obj.get("tokens_per_sec").and_then(|v| v.as_f64()).unwrap_or(0.0); let tok_s = obj.get("tokens_per_sec").and_then(|v| v.as_f64()).unwrap_or(0.0);
println!(); println!();
println!("\x1b[35m━━━ Solmu {} ━━━ {} ━━━\x1b[0m", node_id, model); println!("\x1b[35m━━━ Solmu {} ━━━ {} ━━━\x1b[0m", node_id, model);
println!(" Prompt: \x1b[33m\"{}\"\x1b[0m", prompt); let prompt_preview: String = prompt.chars().take(80).collect();
println!(" Vastaus: \x1b[32m{}\x1b[0m", response); println!(" Prompt: \x1b[33m\"{}...\"\x1b[0m", prompt_preview);
println!(" {} tokenia | {:.0}ms | \x1b[36m{:.1} tok/s\x1b[0m", tok_gen, duration, tok_s); println!(" {} tokenia | {:.0}ms | \x1b[36m{:.1} tok/s\x1b[0m", tok_gen, duration, tok_s);
state.db.increment_tasks(node_id); state.db.increment_tasks(node_id);

View File

@@ -1,5 +1,5 @@
use crossterm::{ use crossterm::{
event::{self, Event, EventStream, KeyCode}, event::{Event, EventStream, KeyCode},
execute, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };

Binary file not shown.

Submodule projektit/projektiopinnot-1-datan-hallinta-ttm23sai added at c20e918b34