Opas-välilehti: GUIDE.md renderöidään sivustolle omana näkymänä
Uusi "Opas"-välilehti (panel-guide) lataa GUIDE.md:n fetchillä ja renderöi sen inline markdown→HTML -parserilla: - Otsikot (h1-h3) GitHub-tyylisesti - Koodiblokit highlight.js-korostuksella - Taulukot (header + body, border-collapse) - Listat (bullet + numeroitu) - Inline-muotoilu: **bold**, *italic*, `code` - Horisontaaliviivat GUIDE.md siirretty static/-hakemistoon jotta hub servaa sen suoraan. Navigointi: #guide hash tai klikkaa "Opas"-välilehteä. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
433
network-poc/static/GUIDE.md
Normal file
433
network-poc/static/GUIDE.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Kipinä Agentic Studio — Opas
|
||||
|
||||
Hajautettu AI-laskentaverkko jossa kielimallit ajavat koodia suoraan selaimessa.
|
||||
Tämä opas selittää miten kielimallit toimivat, miten niitä ohjataan, ja miten
|
||||
tuloksia voi parantaa.
|
||||
|
||||
---
|
||||
|
||||
## Kielimallit ja niiden koot
|
||||
|
||||
Kielimalli on neuroverkko joka ennustaa seuraavan sanan (tokenin) edellisten
|
||||
perusteella. Mallin "koko" tarkoittaa parametrien (painojen) määrää:
|
||||
|
||||
| Malli | Parametrit | Koko levyllä | Nopeus selaimessa | Koodinlaatu |
|
||||
|-------|-----------|-------------|-------------------|-------------|
|
||||
| SmolLM 135M | 135 miljoonaa | ~270 MB | ~5 tok/s | Yksinkertainen teksti |
|
||||
| Qwen2.5-Coder 0.5B | 500 miljoonaa | ~990 MB | ~3-6 tok/s | Pienet funktiot |
|
||||
| Qwen2.5-Coder 3B | 3 miljardia | ~6.2 GB | ~0.4 tok/s | Kokonaiset tiedostot |
|
||||
| GPT-4 (vertailu) | ~1800 miljardia | ~3.6 TB | pilvipalvelu | Kokonaiset projektit |
|
||||
|
||||
**Parametrien vaikutus:** Jokainen parametri on yksi liukuluku (float16 = 2 tavua)
|
||||
joka tallentaa opittua tietoa. 0.5B-malli tietää perusrakenteet mutta tekee
|
||||
loogisia virheitä. 3B-malli ymmärtää kontekstin paremmin. Ero on kuin sanakirjan
|
||||
ja oppikirjan välillä.
|
||||
|
||||
**Miksi selaimessa?** Malli ajetaan käyttäjän omalla laitteella WebAssemblyn
|
||||
kautta. Data ei lähde koneelta, eikä tarvita pilvipalvelua. Haittapuoli on
|
||||
hitaus — GPU-palvelimella sama 0.5B-malli tuottaa ~100 tok/s.
|
||||
|
||||
---
|
||||
|
||||
## Tokenit — kielimallin "sanat"
|
||||
|
||||
Malli ei näe tekstiä kirjaimina vaan **tokeneina**. Tokeni on yleensä
|
||||
sanan osa, kokonainen sana tai välilyönti:
|
||||
|
||||
```
|
||||
"print('Hello')" → ["print", "('", "Hello", "')"] = 4 tokenia
|
||||
"tulosta('Hei')" → ["tul", "osta", "('", "He", "i", "')"] = 6 tokenia
|
||||
```
|
||||
|
||||
**Miksi tällä on väliä?**
|
||||
|
||||
1. **Kustannus:** Jokainen tokeni vaatii laskentaa. 100 tokenin vastaus
|
||||
kestää 0.5B-mallilla ~15-30 sekuntia selaimessa.
|
||||
|
||||
2. **Konteksti-ikkuna:** Malli näkee kerrallaan rajallisen määrän tokeneita.
|
||||
Qwen2.5:ssa ikkuna on 32 768 tokenia. Tähän mahtuu prompti + vastaus.
|
||||
|
||||
3. **Kielen tehokkuus:** Englanti tokenisoidaan tehokkaammin kuin suomi.
|
||||
Sama lause vaatii suomeksi ~30-70% enemmän tokeneita. Siksi promptit
|
||||
ovat englanniksi — malli saa enemmän informaatiota samassa token-budjetissa.
|
||||
|
||||
**Token-budjetti tässä järjestelmässä:**
|
||||
|
||||
| Osa | Tokeneita | Osuus |
|
||||
|-----|-----------|-------|
|
||||
| System prompt | ~30 | kiinteä |
|
||||
| Agent prompt | ~25 | kiinteä |
|
||||
| Konteksti (aiemmat tiedostot) | 0-300 | kasvaa |
|
||||
| Käyttäjän prompti | ~20-50 | vaihtelee |
|
||||
| **Syöte yhteensä** | **~75-400** | |
|
||||
| Generoitu vastaus (max) | 512 | raja |
|
||||
| **Yhteensä** | **~600-900** | /32 768 |
|
||||
|
||||
Konteksti-ikkuna on reilusti riittävä. Pullonkaula ei ole ikkunan koko
|
||||
vaan **mallin kyky ymmärtää pitkää kontekstia** — 0.5B-malli alkaa
|
||||
"unohtaa" ohjeet kun konteksti kasvaa yli ~200 tokenin.
|
||||
|
||||
---
|
||||
|
||||
## Promptit — miten mallia ohjataan
|
||||
|
||||
### Kolmitasoinen prompttirakenne
|
||||
|
||||
```
|
||||
┌─ System prompt (kiinteä, kovakoodattu) ─────────────────────┐
|
||||
│ "You are a coding assistant. Respond with ONLY code." │
|
||||
│ → Määrittää mallin roolin ja rajoitteet │
|
||||
│ → Tämä on tehokkain ohje koska malli priorisoi sen │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─ Agent prompt (käyttäjän muokattavissa) ────────────────────┐
|
||||
│ "Olet kokenut ohjelmistokehittäjä. Kirjoita selkeää..." │
|
||||
│ → Agenttikohtainen persoonallisuus ja erikoisosaaminen │
|
||||
│ → Tallennetaan localStorageen, voi muokata UI:ssa │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─ User prompt (vaihtelee joka kutsussa) ─────────────────────┐
|
||||
│ "Write ONLY the file main.py: FastAPI app with endpoints." │
|
||||
│ → Konkreettinen tehtävä tällä kerralla │
|
||||
│ → Sisältää kontekstin (aiemmat tiedostot) pipeline-tilassa │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─ Prefill (pakottaa vastausformaatin) ───────────────────────┐
|
||||
│ ``` │
|
||||
│ → Malli luulee aloittaneensa koodiblokin ja jatkaa koodilla │
|
||||
│ → Estää "Sure! Here is..." -johdantotekstit │
|
||||
│ → Säästää ~10-20 tokenia per vastaus │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Miksi promptit ovat englanniksi?
|
||||
|
||||
Qwen2.5-Coder on harjoitettu pääosin englanninkielisellä koodilla ja
|
||||
dokumentaatiolla. Suomenkielinen ohje kuluttaa enemmän tokeneita JA
|
||||
malli ymmärtää sen huonommin. Agenttien nimet ja käyttöliittymä ovat
|
||||
suomeksi, mutta tekniset ohjeet mallille englanniksi.
|
||||
|
||||
Poikkeus: agenttipromptit ovat suomeksi koska ne menevät user-blokkiin
|
||||
(ei system-blokkiin) ja niiden tarkoitus on enemmän "persoonallisuus"
|
||||
kuin tekninen ohje.
|
||||
|
||||
---
|
||||
|
||||
## Prefill-tekniikka
|
||||
|
||||
Normaalisti malli päättää vapaasti miten vastaa:
|
||||
|
||||
```
|
||||
Ilman prefilliä:
|
||||
Malli: "Sure! Here is a Python program that prints Hello World:\n```python\nprint('Hello')\n```"
|
||||
→ 25 tokenia, joista 15 turhia
|
||||
|
||||
Prefillin kanssa:
|
||||
Me syötämme: ```
|
||||
Malli jatkaa: python\nprint('Hello')\n```
|
||||
→ 5 tokenia, kaikki hyödyllisiä
|
||||
```
|
||||
|
||||
Prefill on kuin aloittaisit lauseen toisen puolesta — malli jatkaa
|
||||
siitä mihin jäit sen sijaan, että aloittaisi kohteliaalla johdannolla.
|
||||
|
||||
**Sivuvaikutus:** Malli tuottaa kielitunnisteen (`python`, `rust`) ja
|
||||
sulkevan ` ``` `:n. Nämä siivotaan jälkikäteen `strip_markdown_wrapper`-funktiolla.
|
||||
|
||||
---
|
||||
|
||||
## Sampling — miten malli valitsee seuraavan tokenin
|
||||
|
||||
Malli ei "tiedä" oikeaa vastausta. Se laskee jokaiselle mahdolliselle
|
||||
seuraavalle tokenille todennäköisyyden ja valitsee yhden. Valintaa
|
||||
ohjataan kolmella parametrilla:
|
||||
|
||||
### Temperature (0.7)
|
||||
|
||||
Kontrolloi "luovuutta" vs. "varmuutta":
|
||||
|
||||
```
|
||||
Temperature 0.0 (greedy): Aina todennäköisin tokeni → "def fibonacci(n):"
|
||||
Temperature 0.7 (oletus): Painottaa todennäköisiä mutta sallii vaihtelua
|
||||
Temperature 1.5 (luova): Lähes satunnainen → "async lambda fib = ..."
|
||||
```
|
||||
|
||||
0.7 on kompromissi: tarpeeksi determinististä tuottamaan toimivaa koodia,
|
||||
mutta tarpeeksi vaihtelevaa välttämään toistoa.
|
||||
|
||||
### Top-k (40)
|
||||
|
||||
Rajaa valinnan 40 todennäköisimpään tokeniin. Estää mallia valitsemasta
|
||||
täysin absurdeja vaihtoehtoja:
|
||||
|
||||
```
|
||||
Ilman top-k: 150 936 vaihtoehtoa → voi valita minkä tahansa
|
||||
Top-k 40: 40 vaihtoehtoa → järkevät vaihtoehdot
|
||||
Top-k 1: 1 vaihtoehto → greedy (aina sama vastaus)
|
||||
```
|
||||
|
||||
### Repetition penalty (1.15)
|
||||
|
||||
Vähentää jo tuotettujen tokenien todennäköisyyttä. Estää mallia
|
||||
juuttumasta luuppiin:
|
||||
|
||||
```
|
||||
Ilman rangaistusta: "print print print print print..."
|
||||
Penalty 1.15: "print('Hello')\nprint('World')"
|
||||
```
|
||||
|
||||
1.15 on lievä rangaistus — estää pahimman toiston mutta sallii
|
||||
saman avainsanan (esim. `return`) esiintymisen useasti.
|
||||
|
||||
---
|
||||
|
||||
## Stop-sekvenssit — milloin generointi loppuu
|
||||
|
||||
Malli generoi tokeneita kunnes jokin näistä tapahtuu:
|
||||
|
||||
1. **EOS-tokeni** (151645): Mallin oma "loppu"-merkki
|
||||
2. **Max tokens** (512): Kovakoodattu raja
|
||||
3. **Stop-sekvenssi**: Malli alkaa tuottaa selitystä
|
||||
|
||||
```
|
||||
fn fibonacci(n: usize) -> usize {
|
||||
if n <= 1 { return n; }
|
||||
fibonacci(n-1) + fibonacci(n-2)
|
||||
}
|
||||
← Tähän asti koodia, ok
|
||||
// Example usage: ← Stop! Tämä ei ole enää vastausta
|
||||
let result = fibonacci(10); ← Ei generoida
|
||||
```
|
||||
|
||||
Tunnistetut stop-sekvenssit: `### `, `Explanation`, `Note:`, `Output:`,
|
||||
`// Example`, `# Example`. Generointi katkaistaan ja teksti trimmataan
|
||||
stop-kohtaan.
|
||||
|
||||
---
|
||||
|
||||
## Projekti-pipeline — miten agenttitiimi toimii
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Käyttäjä │ "FastAPI + SQLite REST API for users"
|
||||
└────┬─────┘
|
||||
↓
|
||||
┌──────────┐ "Pilko tiedostoiksi"
|
||||
│ Manageri │ → models.py, main.py, pyproject.toml
|
||||
└────┬─────┘
|
||||
↓ (tiedostolista)
|
||||
┌──────────┐ "Kirjoita models.py"
|
||||
│ Koodari │ → from sqlalchemy import ...
|
||||
└────┬─────┘
|
||||
↓ (models.py kontekstina)
|
||||
┌──────────┐ "Kirjoita main.py, käytä models.py:tä"
|
||||
│ Koodari │ → from fastapi import ...
|
||||
└────┬─────┘
|
||||
↓ (kaikki tiedostot kontekstina)
|
||||
┌──────────┐ "Kirjoita pyproject.toml"
|
||||
│ Koodari │ → [project] dependencies = [...]
|
||||
└────┬─────┘
|
||||
↓ (kaikki tiedostot yhdessä)
|
||||
┌──────────┐ "Review: onko bugeja?"
|
||||
│ Testaaja │ → "Missing db.close(), no error handling"
|
||||
└────┬─────┘
|
||||
↓ (review-palaute + koodi)
|
||||
┌──────────┐ "Korjaa nämä ongelmat"
|
||||
│ Koodari │ → Korjattu koodi
|
||||
└────┬─────┘
|
||||
↓
|
||||
┌──────────┐ "Tarkista korjaukset"
|
||||
│ Testaaja │ → "LGTM" tai lisäkorjauksia
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
**Kontekstin ketjutus** on kriittistä: kun koodari kirjoittaa `main.py`:tä,
|
||||
se saa `models.py`:n sisällön promptissa. Ilman tätä se ei tietäisi
|
||||
mitä luokkia importata.
|
||||
|
||||
**Riippuvuusjärjestys:** Manageria pyydetään listaamaan riippuvuudet ensin
|
||||
(models.py ennen main.py) jotta kontekstiketju toimii oikeaan suuntaan.
|
||||
|
||||
---
|
||||
|
||||
## Laadun parantaminen
|
||||
|
||||
### 1. Isompi malli (suurin vaikutus)
|
||||
|
||||
| | 0.5B | 3B | Pilvi-API |
|
||||
|---|---|---|---|
|
||||
| Fibonacci | Joskus virheitä | Yleensä oikein | Aina oikein |
|
||||
| FastAPI CRUD | Voi käyttää Flaskia | Oikea kirjasto | Täydellinen |
|
||||
| Monimutkainen logiikka | Hallusinoi | Osaa perusasiat | Syvä ymmärrys |
|
||||
| Nopeus (selain) | ~5 tok/s | ~0.4 tok/s | — |
|
||||
| Latauksen koko | 990 MB | 6.2 GB | 0 (API) |
|
||||
|
||||
**Käytännössä:** `kpn load 2` lataa 3B-mallin. Hitaampi mutta huomattavasti
|
||||
parempi koodinlaatu. Suositus monimutkaisiin projekteihin.
|
||||
|
||||
### 2. Paremmat promptit (ilmaista)
|
||||
|
||||
**Huono:** `"tee fibonacci"`
|
||||
- Malli ei tiedä kieltä, formaattia tai kontekstia
|
||||
|
||||
**Hyvä:** `"Write a fibonacci function in Rust that returns Vec<u64>"`
|
||||
- Kieli, palautustyyppi ja rakenne määritelty
|
||||
|
||||
**Promptin säännöt:**
|
||||
- Englanniksi (tehokkaampi tokenisointi, parempi ymmärrys)
|
||||
- Konkreettinen (mainitse kieli, kirjastot, palautustyyppi)
|
||||
- Lyhyt (jokainen sana kuluttaa tokenin konteksti-ikkunasta)
|
||||
- Positiivinen ("Write X" ei "Don't write Y")
|
||||
|
||||
### 3. Kontekstin hallinta (pipeline-taso)
|
||||
|
||||
**Ongelma:** 0.5B-malli "unohtaa" promptin alun kun konteksti kasvaa.
|
||||
|
||||
**Ratkaisu:** Pienet, kohdennetut promptit:
|
||||
- Yksi tiedosto kerrallaan (ei "kirjoita koko projekti")
|
||||
- Vain relevantit aiemmat tiedostot kontekstina
|
||||
- Max 4 tiedostoa per projekti
|
||||
|
||||
### 4. Iterointi (review-luuppi)
|
||||
|
||||
Yksi generointikierros tuottaa harvoin virheetöntä koodia.
|
||||
Pipeline-arkkitehtuuri mahdollistaa:
|
||||
|
||||
1. **Generointi** — ensimmäinen versio
|
||||
2. **Review** — testaaja löytää ongelmat
|
||||
3. **Korjaus** — koodari saa palautteen ja korjaa
|
||||
4. **Uusi review** — tarkistetaan korjaukset
|
||||
|
||||
Nykyinen järjestelmä tekee max 1 korjauskierroksen. Useampi
|
||||
iteraatio parantaisi laatua mutta kasvattaisi laskenta-aikaa.
|
||||
|
||||
### 5. Erikoistetut system promptit
|
||||
|
||||
Oletuspromptit ovat yleiskäyttöisiä. Projektikohtaiset promptit
|
||||
parantavat laatua merkittävästi:
|
||||
|
||||
```
|
||||
Oletus: "Olet kokenut ohjelmistokehittäjä."
|
||||
|
||||
Parempi: "You are a Python backend developer specializing in FastAPI.
|
||||
Always use Pydantic models for request/response schemas.
|
||||
Always use dependency injection for database sessions.
|
||||
Follow the repository pattern."
|
||||
```
|
||||
|
||||
Agenttikohtaiset promptit voi muokata suoraan UI:ssa.
|
||||
|
||||
### 6. Few-shot esimerkit
|
||||
|
||||
Malli oppii parhaiten esimerkeistä. Sen sijaan, että sanot "kirjoita
|
||||
FastAPI endpoint", näytä miltä haluat tuloksen näyttävän:
|
||||
|
||||
```
|
||||
Write a GET endpoint like this example:
|
||||
|
||||
@app.get("/items")
|
||||
def list_items():
|
||||
db = SessionLocal()
|
||||
return db.query(Item).all()
|
||||
|
||||
Now write a similar endpoint for /users.
|
||||
```
|
||||
|
||||
0.5B-malli jäljittelee rakennetta tehokkaasti — se on parempi kopioimaan
|
||||
kuin keksimään. Nykyinen pyproject.toml-esimerkki promptissa on tätä tekniikkaa.
|
||||
|
||||
### 7. Temperature-säätö tehtävän mukaan
|
||||
|
||||
Nykyinen temperature 0.7 on kompromissi. Eri tehtävät hyötyisivät eri arvoista:
|
||||
|
||||
| Tehtävä | Paras temperature | Miksi |
|
||||
|---------|-------------------|-------|
|
||||
| Tarkka koodi (CRUD, boilerplate) | 0.2-0.4 | Determinismi tärkeää |
|
||||
| Luova koodi (algoritmit, arkkitehtuuri) | 0.6-0.8 | Vaihtelu löytää ratkaisuja |
|
||||
| Vapaa teksti (kommentit, dokumentaatio) | 0.8-1.0 | Luonnollisempi kieli |
|
||||
|
||||
Järjestelmä voisi valita temperaturen automaattisesti tehtävätyypin perusteella.
|
||||
|
||||
### 8. Ensemble — sama prompti usealle mallille
|
||||
|
||||
Lähetetään sama tehtävä kahdelle solmulle ja valitaan parempi vastaus.
|
||||
Nykyinen Proof of Compute -arkkitehtuuri tukee tätä periaatteessa:
|
||||
hub voisi reitittää saman task_id:n kahdelle solmulle ja verrata tuloksia.
|
||||
|
||||
Käytännössä tämä kaksinkertaistaa laskenta-ajan mutta parantaa laatua
|
||||
merkittävästi — virheellinen vastaus harvoin on sama kahdella ajolla
|
||||
koska sampling on stokastinen.
|
||||
|
||||
### 9. Post-processing (nykyinen)
|
||||
|
||||
Mallin raakavastaus siivotaan:
|
||||
1. Kielitunniste poistetaan (`python`, `rust`, ...)
|
||||
2. Sulkeva ` ``` ` poistetaan
|
||||
3. Johdantolauseet poistetaan ("Sure!", "Here is...")
|
||||
4. Selityskommentit poistetaan ("# This is a simple...")
|
||||
5. Stop-sekvenssit katkaisevat generoinnin
|
||||
|
||||
Tämä ei paranna mallin ajattelua mutta poistaa turhan roskan.
|
||||
|
||||
### 10. Mallin hienosäätö (fine-tuning)
|
||||
|
||||
Qwen2.5-Coder on yleiskäyttöinen koodimalli. Jos sitä hienosäätäisi
|
||||
omalla koodiaineistolla (esim. yrityksen koodikanta, tietty framework),
|
||||
se tuottaisi huomattavasti parempaa koodia juuri siihen kontekstiin.
|
||||
|
||||
LoRA-hienosäätö 0.5B-mallille vaatii ~4 GB GPU-muistia ja muutaman
|
||||
tunnin harjoittelua. Tulos on erikoistunut malli joka osaa tuottaa
|
||||
esimerkiksi juuri FastAPI + SQLAlchemy -koodia luotettavasti.
|
||||
|
||||
---
|
||||
|
||||
## Välimuistiarkkitehtuuri — miksi toinen lataus on nopea
|
||||
|
||||
```
|
||||
Ensimmäinen lataus (hidas):
|
||||
Verkko (HuggingFace CDN) → IndexedDB → RAM → Mallin rakennus
|
||||
~990 MB lataus, ~30-60s
|
||||
|
||||
Toinen lataus samalla sivulatauksella (nopea):
|
||||
RAM-cache → Mallia ei rakenneta uusiksi, vain KV-cache nollataan
|
||||
~0ms
|
||||
|
||||
Refresh jälkeen (keskitaso):
|
||||
IndexedDB → RAM → Mallin rakennus
|
||||
~0 MB lataus, ~2-5s rakennus
|
||||
|
||||
Uusi selain/laite (hidas):
|
||||
Verkko → IndexedDB → RAM → Mallin rakennus
|
||||
Kuten ensimmäinen lataus
|
||||
```
|
||||
|
||||
**KV-cache:** Mallin sisäinen muisti joka tallentaa aiempien tokenien
|
||||
laskenta tulokset. Nollataan (`clear_kv_cache()`) jokaisen promptin
|
||||
välillä jotta edellinen vastaus ei vuoda seuraavaan.
|
||||
|
||||
---
|
||||
|
||||
## Lukuja käytännöstä
|
||||
|
||||
**Yksittäinen funktio** (esim. fibonacci):
|
||||
- Input: ~80 tokenia
|
||||
- Output: ~50-100 tokenia
|
||||
- Aika: ~10-20s (0.5B, selain)
|
||||
- Laatu: Yleensä toimiva, joskus loogisia virheitä
|
||||
|
||||
**3 tiedoston projekti** (esim. FastAPI CRUD):
|
||||
- Manageri: ~30 tok out
|
||||
- Koodari (3x): ~100-150 tok out per tiedosto
|
||||
- Testeri: ~50 tok out
|
||||
- Korjaukset: ~100 tok out (jos tarpeen)
|
||||
- **Yhteensä: ~500-700 tokenia, ~3-5 min**
|
||||
- Laatu: Rakenne oikein, yksittäisiä bugeja
|
||||
|
||||
**Token-kustannus vs. pilvipalvelu:**
|
||||
- Tässä järjestelmässä: 0 euroa (laskenta omalla koneella)
|
||||
- GPT-4 API: ~700 tokenia x $0.03/1K = ~$0.02 per projekti
|
||||
- Claude API: ~700 tokenia x $0.015/1K = ~$0.01 per projekti
|
||||
|
||||
Selaimessa ajettava malli on ilmainen mutta huomattavasti hitaampi
|
||||
ja heikompilaatuinen kuin pilvi-API. Sopii oppimiseen, prototypointiin
|
||||
ja tilanteisiin joissa data ei saa lähteä omalta koneelta.
|
||||
@@ -143,6 +143,13 @@
|
||||
}
|
||||
.code-output .hljs { background: transparent; padding: 0; }
|
||||
|
||||
#guide-content { scrollbar-color: #30363d transparent; }
|
||||
#guide-content h1 { color: #e6edf3; }
|
||||
#guide-content h2 { color: #e6edf3; }
|
||||
#guide-content a { color: #58a6ff; }
|
||||
#guide-content table { border: 1px solid #30363d; border-radius: 6px; overflow: hidden; }
|
||||
#guide-content pre { scrollbar-color: #30363d transparent; }
|
||||
|
||||
.code-task-card {
|
||||
background: #0d1117;
|
||||
border: 1px solid var(--border-color);
|
||||
@@ -701,6 +708,7 @@
|
||||
<div class="main-tab active" onclick="switchMainTab('network')" data-i18n="tab_network">Laskentaverkko</div>
|
||||
<div class="main-tab" onclick="switchMainTab('codelab')" data-i18n="tab_codelab">Koodilaboratorio</div>
|
||||
<div class="main-tab" onclick="switchMainTab('agents')" data-i18n="tab_agents">Kipinä Agentic Playground</div>
|
||||
<div class="main-tab" onclick="switchMainTab('guide')" data-i18n="tab_guide">Opas</div>
|
||||
</div>
|
||||
|
||||
<!-- PANEELI 1: Laskentaverkko -->
|
||||
@@ -1115,6 +1123,13 @@
|
||||
</div>
|
||||
</div><!-- /panel-agents -->
|
||||
|
||||
<!-- PANEELI 4: Opas -->
|
||||
<div id="panel-guide" class="main-panel">
|
||||
<div id="guide-content" style="max-width:800px;margin:0 auto;padding:20px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;color:var(--text-color);line-height:1.7;font-size:15px">
|
||||
<p style="color:#8b949e">Ladataan opasta...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
@@ -1469,7 +1484,7 @@
|
||||
|
||||
// URL-hash navigointi
|
||||
const initHash = window.location.hash.replace('#', '');
|
||||
if (['codelab', 'agents'].includes(initHash)) {
|
||||
if (['codelab', 'agents', 'guide'].includes(initHash)) {
|
||||
switchMainTab(initHash);
|
||||
}
|
||||
|
||||
@@ -3463,6 +3478,111 @@ Write the corrected code.`;
|
||||
if (window.selectAgent) window.selectAgent('client');
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// GUIDE.md:n lataus ja renderöinti
|
||||
(async function loadGuide() {
|
||||
const container = document.getElementById('guide-content');
|
||||
if (!container) return;
|
||||
try {
|
||||
const res = await fetch('/GUIDE.md');
|
||||
if (!res.ok) { container.innerHTML = '<p style="color:#f85149">Oppaan lataus epäonnistui.</p>'; return; }
|
||||
const md = await res.text();
|
||||
container.innerHTML = renderMarkdown(md);
|
||||
// Syntaksikorostus koodiblokeille
|
||||
container.querySelectorAll('pre code').forEach(block => {
|
||||
if (typeof hljs !== 'undefined') hljs.highlightElement(block);
|
||||
});
|
||||
} catch(e) {
|
||||
container.innerHTML = '<p style="color:#f85149">Virhe: ' + e.message + '</p>';
|
||||
}
|
||||
})();
|
||||
|
||||
function renderMarkdown(md) {
|
||||
const lines = md.split('\n');
|
||||
let html = '';
|
||||
let inCode = false;
|
||||
let codeLang = '';
|
||||
let codeBuffer = '';
|
||||
let inTable = false;
|
||||
let tableRows = [];
|
||||
|
||||
function flushTable() {
|
||||
if (!inTable) return;
|
||||
inTable = false;
|
||||
if (tableRows.length < 2) return;
|
||||
const headerCells = tableRows[0].split('|').filter(c => c.trim());
|
||||
const bodyRows = tableRows.slice(2); // Skip header + separator
|
||||
html += '<div style="overflow-x:auto;margin:16px 0"><table style="width:100%;border-collapse:collapse;font-size:14px">';
|
||||
html += '<thead><tr>' + headerCells.map(c => `<th style="text-align:left;padding:8px 12px;border-bottom:2px solid #30363d;color:#58a6ff;font-weight:600">${inlineFormat(c.trim())}</th>`).join('') + '</tr></thead>';
|
||||
html += '<tbody>';
|
||||
for (const row of bodyRows) {
|
||||
const cells = row.split('|').filter(c => c.trim());
|
||||
if (cells.length === 0) continue;
|
||||
html += '<tr>' + cells.map(c => `<td style="padding:6px 12px;border-bottom:1px solid #21262d">${inlineFormat(c.trim())}</td>`).join('') + '</tr>';
|
||||
}
|
||||
html += '</tbody></table></div>';
|
||||
tableRows = [];
|
||||
}
|
||||
|
||||
function inlineFormat(text) {
|
||||
return text
|
||||
.replace(/`([^`]+)`/g, '<code style="background:#161b22;padding:2px 6px;border-radius:3px;font-size:13px;color:#e6edf3">$1</code>')
|
||||
.replace(/\*\*([^*]+)\*\*/g, '<strong style="color:#e6edf3">$1</strong>')
|
||||
.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
||||
}
|
||||
|
||||
for (const line of lines) {
|
||||
// Koodiblokit
|
||||
if (line.startsWith('```')) {
|
||||
if (inCode) {
|
||||
html += `<pre style="background:#0d1117;border:1px solid #30363d;border-radius:6px;padding:14px;margin:12px 0;overflow-x:auto"><code class="language-${codeLang}">${codeBuffer.replace(/</g,'<')}</code></pre>`;
|
||||
inCode = false;
|
||||
codeBuffer = '';
|
||||
} else {
|
||||
flushTable();
|
||||
inCode = true;
|
||||
codeLang = line.slice(3).trim() || 'plaintext';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inCode) { codeBuffer += (codeBuffer ? '\n' : '') + line; continue; }
|
||||
|
||||
// Taulukot
|
||||
if (line.includes('|') && line.trim().startsWith('|')) {
|
||||
if (!inTable) inTable = true;
|
||||
tableRows.push(line);
|
||||
continue;
|
||||
} else {
|
||||
flushTable();
|
||||
}
|
||||
|
||||
// Tyhjä rivi
|
||||
if (!line.trim()) { html += '<div style="height:8px"></div>'; continue; }
|
||||
|
||||
// Otsikot
|
||||
if (line.startsWith('# ')) { html += `<h1 style="color:#e6edf3;font-size:28px;margin:32px 0 12px;border-bottom:1px solid #30363d;padding-bottom:8px">${inlineFormat(line.slice(2))}</h1>`; continue; }
|
||||
if (line.startsWith('## ')) { html += `<h2 style="color:#e6edf3;font-size:22px;margin:28px 0 10px;border-bottom:1px solid #21262d;padding-bottom:6px">${inlineFormat(line.slice(3))}</h2>`; continue; }
|
||||
if (line.startsWith('### ')) { html += `<h3 style="color:#e6edf3;font-size:17px;margin:20px 0 8px">${inlineFormat(line.slice(4))}</h3>`; continue; }
|
||||
|
||||
// Horisontaalinen viiva
|
||||
if (line.match(/^-{3,}$/)) { html += '<hr style="border:none;border-top:1px solid #30363d;margin:20px 0">'; continue; }
|
||||
|
||||
// Lista
|
||||
if (line.match(/^[\-\*] /)) {
|
||||
html += `<div style="padding:2px 0 2px 20px">${inlineFormat(line.replace(/^[\-\*] /, '• '))}</div>`;
|
||||
continue;
|
||||
}
|
||||
if (line.match(/^\d+\. /)) {
|
||||
html += `<div style="padding:2px 0 2px 20px">${inlineFormat(line)}</div>`;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normaali tekstirivi
|
||||
html += `<p style="margin:4px 0">${inlineFormat(line)}</p>`;
|
||||
}
|
||||
flushTable();
|
||||
return html;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user